File

src/utility/configurablesDataSearch/dataConfigurableDataSearch.ts

Extends

DataConfigurableParamValues

Implements

DataConfigurableDataSearchI

Index

Properties
Methods

Constructor

constructor(injector: Injector, distributionDetails: DistributionDetails, paramValues: Array, spatialOverrides?: BoundingBox, temporalOverrides?: TemporalRange)
Parameters :
Name Type Optional
injector Injector No
distributionDetails DistributionDetails No
paramValues Array<ParameterValue> No
spatialOverrides BoundingBox Yes
temporalOverrides TemporalRange Yes

Properties

Public Readonly actions
Type : Array<DataConfigurableAction>
Inherited from DataConfigurable
Public Readonly isDataConfigurableDataSearchLoading
Type : boolean
Private layerBboxSrc
Default value : new BehaviorSubject<number[] | null>(null)
Public levels
Type : Array<DistributionLevel>
Public pinnedObs
Default value : this.pinnedSrc.asObservable()
Private Readonly pinnedSrc
Default value : new BehaviorSubject<boolean>(false)
Public selectedObs
Default value : this.selectedSrc.asObservable()
Private Readonly selectedSrc
Default value : new BehaviorSubject<boolean>(false)
Public changed
Default value : false
Inherited from DataConfigurable

Whether any of the #newParamValues have changed or not.

Public Readonly context
Type : string
Inherited from DataConfigurable
Public Readonly currentParamValues
Type : Array<ParameterValue>
Inherited from DataConfigurable

An array of ParameterValue objects representing values that have been applied to any visualizations etc.

Public Readonly distributionDetails
Type : DistributionDetails
Inherited from DataConfigurable
The detailed search result (distribution) object that a configuration is applied to.
Public Readonly id
Type : string
Inherited from DataConfigurable

the object's identifier.

Public Readonly isDownloadable
Type : boolean
Inherited from DataConfigurable

Copied from DistributionDetails

Public Readonly isGraphable
Type : boolean
Inherited from DataConfigurable

Copied from DistributionDetails

Public Readonly isMappable
Type : boolean
Inherited from DataConfigurable

Copied from DistributionDetails

Public Readonly isTabularable
Type : boolean
Inherited from DataConfigurable

Copied from DistributionDetails

Public loading
Default value : false
Inherited from DataConfigurable

Whether the #newParamValues are in the process of being applied or not.

Public Readonly name
Type : string
Inherited from DataConfigurable

the object's name.

Private newParamValues
Type : Array<ParameterValue>
Inherited from DataConfigurable

An array of ParameterValue objects that have been changed and have not yet been applied to any visualizations etc.

Private Readonly parameterDefinitions
Type : ParameterDefinitions
Inherited from DataConfigurable

The ParameterDefinitions retrieved from a DistributionDetails item.

Public sameAsDefaults
Default value : true
Inherited from DataConfigurable

Whether all of the #newParamValues are the same as the defaults or not.

Private Readonly showSpatialCoverageSrc
Default value : new BehaviorSubject<boolean>(false)
Inherited from DataConfigurable

Whether a user has selected to show the spatial coverage for this item or not.

Public showSpatialObs
Default value : this.showSpatialCoverageSrc.asObservable()
Inherited from DataConfigurable
Private spatialLinked
Default value : false
Inherited from DataConfigurable

Whether any spatial parameter values in this configuration are linked to page-wide values.

Public Readonly styleObs
Default value : this.styleSource.asObservable()
Inherited from DataConfigurable
Private Readonly styleSource
Default value : new BehaviorSubject<null | Style>(null)
Inherited from DataConfigurable

An rxjs/BehaviorSubject holding the Style object for this configurable.

Private temporalLinked
Default value : false
Inherited from DataConfigurable

Whether any temporal parameter values in this configuration are linked to page-wide values.

Protected triggerReloadFunc
Type : function
Inherited from DataConfigurable

A function that will trigger a reload of visual representations of this object.

Public valid
Default value : true
Inherited from DataConfigurable

Whether all #newParamValues are valid or not.

Methods

Private copyToClipboard
copyToClipboard(val: string)
Parameters :
Name Type Optional
val string No
Returns : boolean
Private copyToClipboardWithDelay
copyToClipboardWithDelay(text: null | string)
Parameters :
Name Type Optional
text null | string No
Returns : void
Protected doApplyAction
doApplyAction(newCurrentParams: Array, newNewParams?: Array)

The function doApplyAction sets loading to true, creates a new DataConfigurableDataSearch object with updated parameters, and reloads the data with the new configuration. that are used as input for the doApplyAction method. array of ParameterValue objects. It is used to update the parameters of a DataConfigurableDataSearch instance along with the newCurrentParams. If newNewParams is provided, it

Parameters :
Name Type Optional Description
newCurrentParams Array<ParameterValue> No
  • The newCurrentParams parameter is an array of ParameterValue objects that are used as input for the doApplyAction method.
newNewParams Array<ParameterValue> Yes
  • The newNewParams parameter in the doApplyAction method is an optional array of ParameterValue objects. It is used to update the parameters of a DataConfigurableDataSearch instance along with the newCurrentParams. If newNewParams is provided, it

The method doApplyAction is returning an instance of DataConfigurableDataSearch.

Private doCopyUrlAction
doCopyUrlAction()
Returns : void
Private doSetToDefaultsAction
doSetToDefaultsAction()
Returns : void
Public getLayerBbox
getLayerBbox()
Returns : any
Public getLevels
getLevels()
Public getOriginatorUrl
getOriginatorUrl(fromNewParamValue)

The function getOriginatorUrl returns a promise that resolves to either null or a string representing the originator URL. indicates whether to use the new parameter values or the current parameter values. If fromNewParamValue is true, the method will use the new parameter values; otherwise, it will use the current parameter values.

Parameters :
Name Optional Default value Description
fromNewParamValue No false
  • The fromNewParamValue parameter is a boolean value that indicates whether to use the new parameter values or the current parameter values. If fromNewParamValue is true, the method will use the new parameter values; otherwise, it will use the current parameter values.
Returns : Promise<null | string>

a Promise that resolves to either null or a string.

Public isPinned
isPinned()
Returns : boolean
Public isSelected
isSelected()
Returns : boolean
Static makeFromSimpleObject
makeFromSimpleObject(object: Record, injector: Injector, context: string)
Parameters :
Name Type Optional
object Record<string | > No
injector Injector No
context string No
Public setLayerBbox
setLayerBbox(layerBbox: Array | null)
Parameters :
Name Type Optional
layerBbox Array<number> | null No
Returns : this
Public setLevels
setLevels(levels: Array<DistributionLevel>)
Parameters :
Name Type Optional
levels Array<DistributionLevel> No
Public setPinned
setPinned(pinned: boolean)
Parameters :
Name Type Optional
pinned boolean No
Public setSelected
setSelected(selected: boolean)
Parameters :
Name Type Optional
selected boolean No
Public toSimpleObject
toSimpleObject()
Returns : Record<string, >
Public updateLinkedSpatialParams
updateLinkedSpatialParams(boundingBox: BoundingBox, newParams)
Parameters :
Name Type Optional Default value
boundingBox BoundingBox No
newParams No true
Returns : void
Public updateLinkedTemporalParams
updateLinkedTemporalParams(tempRange: TemporalRange, newParams)
Parameters :
Name Type Optional Default value
tempRange TemporalRange No
newParams No true
Returns : void
Protected evaluateActionVisibility
evaluateActionVisibility()
Inherited from DataConfigurable

Calls the DataConfigurableAction function on all #actions.

Returns : void
Public getCurrentSpatialBounds
getCurrentSpatialBounds()
Inherited from DataConfigurable
Returns : BoundingBox
Public getCurrentTemporalRange
getCurrentTemporalRange()
Inherited from DataConfigurable
Returns : TemporalRange
Public getDistributionDetails
getDistributionDetails()
Inherited from DataConfigurable

Retrieves the DistributionDetails that this object was created with.

Public getDownloadableFormats
getDownloadableFormats()
Inherited from DataConfigurable

Returns an Array of DistributionFormats available on this configurable, that are classified as downloadable.

Public getGraphableFormats
getGraphableFormats()
Inherited from DataConfigurable

Returns an Array of DistributionFormats available on this configurable, that are classified as graphable.

Public getMappableableFormats
getMappableableFormats()
Inherited from DataConfigurable

Returns an Array of DistributionFormats available on this configurable, that are classified as mappable.

Public getNewParameterValues
getNewParameterValues()
Inherited from DataConfigurable

Retrieves the #newParamValues variable.

Returns : Array<ParameterValue>
Public getNewSpatialBounds
getNewSpatialBounds()
Inherited from DataConfigurable
Returns : BoundingBox
Public getNewTemporalRange
getNewTemporalRange()
Inherited from DataConfigurable
Returns : TemporalRange
Public getParameterDefinitions
getParameterDefinitions()
Inherited from DataConfigurable

Retrieves the ParameterDefinitions from the DistributionDetails that this object was created with.

Public getShowSpatialCoverage
getShowSpatialCoverage()
Inherited from DataConfigurable
Returns : boolean
Public getSpatialCoverage
getSpatialCoverage()
Inherited from DataConfigurable
Public getStyle
getStyle()
Inherited from DataConfigurable

Returns the style in #styleSource.

Returns : null | Style
Public isOnlyDownloadable
isOnlyDownloadable()
Inherited from DataConfigurable
Returns : boolean
Public isSpatialLinked
isSpatialLinked()
Inherited from DataConfigurable

returns the value of the the #spatialLinked variable.

Returns : boolean
Public isTemporalLinked
isTemporalLinked()
Inherited from DataConfigurable
Returns : boolean
Protected paramsSameAsDefaults
paramsSameAsDefaults()
Inherited from DataConfigurable
Returns : boolean
Protected paramValuesSame
paramValuesSame(params1: Array, params2: Array)
Inherited from DataConfigurable

This function compares two Arrays of ParameterValues. As default value on param definitions are now set to "" on init if not set, ths number of param values should be constant.

Parameters :
Name Type Optional Description
params1 Array<ParameterValue> No

First set of ParameterValues.

params2 Array<ParameterValue> No

Second set of ParameterValues.

Returns : boolean

true if all values in params1 are equivalent to the item with the same key in params2. As default value on param definitions are now set to "" on init if not set, ths number of param values should be constant.

Public reload
reload(newConfigurable?: DataConfigurable)
Inherited from DataConfigurable

The reload function in TypeScript triggers a reload with optional new configuration after a delay. method is an optional parameter of type DataConfigurable. It is used to provide a new configuration for reloading data. If a newConfigurable object is provided, it will be used for reloading data; otherwise, the method will which the method is being called.

Parameters :
Name Type Optional Description
newConfigurable DataConfigurable Yes
  • The newConfigurable parameter in the reload method is an optional parameter of type DataConfigurable. It is used to provide a new configuration for reloading data. If a newConfigurable object is provided, it will be used for reloading data; otherwise, the method will
Protected resetParameterValues
resetParameterValues()
Inherited from DataConfigurable

Sets the #newParamValues variable to a copy of the #currentParamValues variable, as it was after initialisation.

Returns : void
Public setLoading
setLoading(loading: boolean)
Inherited from DataConfigurable

Sets the internal #loading variable before calling #updateActionsEnabledStatus.

Parameters :
Name Type Optional
loading boolean No
Public setNewParams
setNewParams(newParamValues: Array)
Inherited from DataConfigurable

The function setNewParams updates parameter values and related properties, and returns the instance for chaining. parameter values. the class.

Parameters :
Name Type Optional Description
newParamValues Array<ParameterValue> No
  • An array of new parameter values that will be used to update the current parameter values.
Protected setParameterValuesToDefaults
setParameterValuesToDefaults()
Inherited from DataConfigurable

Sets the #newParamValues variable to the default values obtained from the #distributionDetails object.

Returns : void
Public setShowSpatialCoverage
setShowSpatialCoverage(show: boolean)
Inherited from DataConfigurable
Parameters :
Name Type Optional
show boolean No
Public setSpatialLinked
setSpatialLinked(linked: boolean)
Inherited from DataConfigurable

Sets the value of the the #spatialLinked variable and calls reload.

Parameters :
Name Type Optional Description
linked boolean No

Whether spatially linked or not.

Public setStyle
setStyle(style: null | Style, force)
Inherited from DataConfigurable

The function setStyle updates the style of an element if it is different from the current style or if forced to do so. or an object of type Style. a default value of false. It is used to determine whether the style should be set even if it is the same as the current style. If force is set to true, the style will be

Parameters :
Name Type Optional Default value Description
style null | Style No
  • The style parameter in the setStyle method can be either null or an object of type Style.
force No false
  • The force parameter in the setStyle method is a boolean parameter with a default value of false. It is used to determine whether the style should be set even if it is the same as the current style. If force is set to true, the style will be
Public setTemporalLinked
setTemporalLinked(linked: boolean)
Inherited from DataConfigurable
Parameters :
Name Type Optional
linked boolean No
Public setTriggerReloadFunc
setTriggerReloadFunc(func: (configurable: DataConfigurable) => void)
Inherited from DataConfigurable
Parameters :
Name Type Optional
func function No
Public setValid
setValid(valid: boolean)
Inherited from DataConfigurable

Sets the internal #valid variable before calling #updateActionsEnabledStatus.

Parameters :
Name Type Optional Description
valid boolean No

Whether this object is valid or not.

Protected updateActionsEnabledStatus
updateActionsEnabledStatus()
Inherited from DataConfigurable

Calls the DataConfigurableAction function on all #actions using the #valid, #changed and #loading values.

Returns : void
Public watchStyle
use styleobs instead
watchStyle()
Inherited from DataConfigurable

Returns an rxjs/Observable object from #styleSource, containing the current style.

import { ParameterValue } from 'api/webApi/data/parameterValue.interface';
import { DistributionDetails } from 'api/webApi/data/distributionDetails.interface';
import { Injector } from '@angular/core';
import { DataConfigurableAction, DataConfigurableActionType } from 'utility/configurables/dataConfigurableAction';
import { SearchService } from 'services/search.service';
import { SimpleParameterValue } from 'api/webApi/data/impl/simpleParameterValue';
import { Style } from 'utility/styler/style';
import { DataConfigurableParamValues } from 'utility/configurables/dataConfigurableParamValues';
import { BoundingBox } from 'api/webApi/data/boundingBox.interface';
import { TemporalRange } from 'api/webApi/data/temporalRange.interface';
import { SimpleBoundingBox } from 'api/webApi/data/impl/simpleBoundingBox';
import { SimpleTemporalRange } from 'api/webApi/data/impl/simpleTemporalRange';
import { ExecutionService } from 'services/execution.service';
import { BehaviorSubject } from 'rxjs';
import { DataConfigurableDataSearchI } from './dataConfigurableDataSearchI.interface';
import { DistributionLevel } from 'api/webApi/data/distributionLevel.interface';
import { SimpleDistributionLevel } from 'api/webApi/data/impl/SimpleDistributionLevel';
import { NotificationService } from 'services/notification.service';
import { Tracker } from 'utility/tracker/tracker.service';
import { TrackerAction, TrackerCategory } from 'utility/tracker/tracker.enum';


export class DataConfigurableDataSearch
  extends DataConfigurableParamValues
  implements DataConfigurableDataSearchI {

  public readonly isDataConfigurableDataSearchLoading: boolean;
  public readonly actions: Array<DataConfigurableAction>;
  public levels: Array<DistributionLevel>;

  private readonly pinnedSrc = new BehaviorSubject<boolean>(false);
  // eslint-disable-next-line @typescript-eslint/member-ordering
  public pinnedObs = this.pinnedSrc.asObservable();


  private layerBboxSrc = new BehaviorSubject<number[] | null>(null);


  private readonly selectedSrc = new BehaviorSubject<boolean>(false);
  // eslint-disable-next-line @typescript-eslint/member-ordering
  public selectedObs = this.selectedSrc.asObservable();

  constructor(
    injector: Injector,
    distributionDetails: DistributionDetails,
    paramValues: Array<ParameterValue>,
    spatialOverrides?: BoundingBox,
    temporalOverrides?: TemporalRange,
  ) {
    super(injector, distributionDetails, paramValues, spatialOverrides, temporalOverrides);


    const copyUrlAction = new DataConfigurableAction(
      'Copy URL',
      () => this.doCopyUrlAction(),
      () => (!this.changed && this.valid),
    );
    copyUrlAction.setActionTypeAction(DataConfigurableActionType.LINK);

    // set up actions
    this.actions = [
      copyUrlAction,
      new DataConfigurableAction(
        'Set to defaults',
        () => this.doSetToDefaultsAction(),
        () => (!this.loading && !this.sameAsDefaults),
      ),
      new DataConfigurableAction(
        'Apply',
        () => this.doApplyAction(this.getNewParameterValues()),
        () => (!this.loading && this.changed && this.valid),
      ),
    ];

    this.evaluateActionVisibility();
  }
  // should be kept in sync with object "toSimpleObject" method
  public static makeFromSimpleObject(
    object: Record<string, unknown>,
    injector: Injector,
    context: string,
  ): Promise<null | DataConfigurableDataSearch> {
    const searchService = injector.get(SearchService);
    return Promise.resolve<null | DataConfigurableDataSearch>(searchService.getDetailsById(String(object.id), context)
      .then((dist: DistributionDetails) => {
        const paramValues = (object.paramValues as Array<Record<string, unknown>>)
          .map((obj: Record<string, unknown>) => new SimpleParameterValue(String(obj.name), String(obj.value)));
        const layerBbox: Array<number> = [];
        const levels = (object.levels as Array<Record<string, unknown>>)
          .map((obj: Record<string, unknown>) => new SimpleDistributionLevel(Number(obj.id), String(obj.value)));
        return new DataConfigurableDataSearch(injector, dist, paramValues)
          .setStyle(Style.makeFromSimpleObject(object.style as Record<string, string | number>))
          .setPinned(!!object.pinned)
          .setSelected(!!object.selected)
          .setSpatialLinked(!!object.spatialLinked)
          .setTemporalLinked(!!object.temporalLinked)
          .setShowSpatialCoverage(!!object.showSpatialCoverage)
          .setLevels(levels)
          .setLayerBbox(layerBbox);
      })
      .catch(() => null)
    );
  }

  public setLayerBbox(layerBbox: Array<number> | null){
    this.layerBboxSrc.next(layerBbox);
    return this;
  }
  public getLayerBbox(){
    return this.layerBboxSrc.value;
  }


  public isPinned(): boolean {
    return this.pinnedSrc.value;
  }
  public setPinned(pinned: boolean): this {
    this.pinnedSrc.next(pinned);
    return this;
  }

  public setLevels(levels: Array<DistributionLevel>): this {
    this.levels = levels;
    return this;
  }

  public getLevels(): Array<DistributionLevel> {
    return this.levels;
  }

  public isSelected(): boolean {
    return this.selectedSrc.value;
  }
  public setSelected(selected: boolean): this {
    this.selectedSrc.next(selected);
    return this;
  }

  public updateLinkedSpatialParams(boundingBox: BoundingBox, newParams = true): void {
    if (this.isSpatialLinked()) {
      const paramDefs = this.getParameterDefinitions();
      const updatedNewParams = paramDefs.updateSpatialParamsUsingBounds(boundingBox, this.getNewParameterValues());
      if (newParams || (!SimpleBoundingBox.isDifferent(this.getCurrentSpatialBounds(), boundingBox))) {
        this.setNewParams(updatedNewParams);
      } else {
        const updatedCurrentParams = paramDefs.updateSpatialParamsUsingBounds(boundingBox, this.currentParamValues.slice());
        this.doApplyAction(updatedCurrentParams, updatedNewParams);
      }
    }
  }

  public updateLinkedTemporalParams(tempRange: TemporalRange, newParams = true): void {
    if (this.isTemporalLinked()) {
      const paramDefs = this.getParameterDefinitions();
      const updatedNewParams = paramDefs.updateTemporalParamsUsingRange(tempRange, this.getNewParameterValues());
      if (newParams || (!SimpleTemporalRange.isDifferent(this.getCurrentTemporalRange(), tempRange))) {
        this.setNewParams(updatedNewParams);
      } else {
        const updatedCurrentParams = paramDefs.updateTemporalParamsUsingRange(tempRange, this.currentParamValues.slice());
        this.doApplyAction(updatedCurrentParams, updatedNewParams);
      }
    }
  }

  // should be kept in sync with static "makeFromSimpleObject" method
  public toSimpleObject(): Record<string, unknown> {
    return {
      id: this.id,
      paramValues: this.currentParamValues.slice(),
      style: this.getStyle(),
      pinned: this.isPinned(),
      selected: this.isSelected(),
      spatialLinked: this.isSpatialLinked(),
      temporalLinked: this.isTemporalLinked(),
      showSpatialCoverage: this.getShowSpatialCoverage(),
      levels: this.getLevels(),
      layerBbox: this.getLayerBbox(),
    };
  }

  /**
   * The function `getOriginatorUrl` returns a promise that resolves to either `null` or a string
   * representing the originator URL.
   * @param [fromNewParamValue=false] - The `fromNewParamValue` parameter is a boolean value that
   * indicates whether to use the new parameter values or the current parameter values. If
   * `fromNewParamValue` is `true`, the method will use the new parameter values; otherwise, it will use
   * the current parameter values.
   * @returns a Promise that resolves to either null or a string.
   */
  public getOriginatorUrl(fromNewParamValue = false): Promise<null | string> {
    const exe = this.injector.get(ExecutionService);

    const paramValues = fromNewParamValue ? this.getNewParameterValues().slice() : this.currentParamValues.slice();
    return exe.getOriginatorUrl(this.distributionDetails, this.getParameterDefinitions(), paramValues);
  }

  /**
   * The function `doApplyAction` sets loading to true, creates a new `DataConfigurableDataSearch`
   * object with updated parameters, and reloads the data with the new configuration.
   * @param newCurrentParams - The `newCurrentParams` parameter is an array of `ParameterValue` objects
   * that are used as input for the `doApplyAction` method.
   * @param [newNewParams] - The `newNewParams` parameter in the `doApplyAction` method is an optional
   * array of `ParameterValue` objects. It is used to update the parameters of a
   * `DataConfigurableDataSearch` instance along with the `newCurrentParams`. If `newNewParams` is
   * provided, it
   * @returns The method `doApplyAction` is returning an instance of `DataConfigurableDataSearch`.
   */
  protected doApplyAction(
    newCurrentParams: Array<ParameterValue>,
    newNewParams?: Array<ParameterValue>,
  ): DataConfigurableDataSearch {
    this.setLoading(true);

    const tracker = this.injector.get(Tracker);
    tracker.trackEvent(TrackerCategory.DISTRIBUTION, TrackerAction.APPLY_PARAMETERS, this.distributionDetails.getDomainCode() + Tracker.TARCKER_DATA_SEPARATION + this.distributionDetails.getName());

    const newConfigurable = new DataConfigurableDataSearch(
      this.injector,
      this.distributionDetails,
      newCurrentParams,
    )
      .setStyle(this.getStyle())
      .setPinned(this.isPinned())
      .setSelected(this.isSelected())
      .setSpatialLinked(this.isSpatialLinked())
      .setTemporalLinked(this.isTemporalLinked())
      .setShowSpatialCoverage(this.getShowSpatialCoverage())
      .setLevels(this.getLevels())
      .setLayerBbox(this.getLayerBbox());

    if (null != newNewParams) {
      newConfigurable.setNewParams(newNewParams);
    }

    this.reload(newConfigurable);

    return newConfigurable;
  }

  private doSetToDefaultsAction(): void {
    this.setParameterValuesToDefaults();
    this.reload();
  }

  private doCopyUrlAction(): void {
    if ((null != this.distributionDetails) && (null != this.injector)) {

      void this.getOriginatorUrl()
        .then(url => {
          this.copyToClipboardWithDelay(url);
        });
    }

  }


  private copyToClipboardWithDelay(text: null | string): void {
    console.log(text);

    if (null != text) {
      // try to add to clipboard (fails in some ie)
      setTimeout(() => {

        let success = false;
        try {
          success = this.copyToClipboard(text);
        } finally {
          if (null != this.injector) {
            const notifier = this.injector.get(NotificationService);
            if (success) {
              notifier.sendNotification('Successfully copied URL', 'x', NotificationService.TYPE_SUCCESS, 5000);
            } else {
              notifier.sendNotification('Failed to copy URL', 'x', NotificationService.TYPE_ERROR, 5000);
            }
          }
        }
      }, 100);
    }
  }

  private copyToClipboard(val: string): boolean {
    const selBox = document.createElement('textarea');
    selBox.style.position = 'fixed';
    selBox.style.left = '0';
    selBox.style.top = '0';
    selBox.style.opacity = '0';
    selBox.value = val;
    document.body.appendChild(selBox);
    selBox.focus();
    selBox.select();
    const success = document.execCommand('copy');
    document.body.removeChild(selBox);
    return success;
  }

}

results matching ""

    No results matching ""