diff --git a/src/main/js/apps/sample/app.json b/src/main/js/apps/sample/app.json
index 893dd29..0165f99 100644
--- a/src/main/js/apps/sample/app.json
+++ b/src/main/js/apps/sample/app.json
@@ -72,6 +72,19 @@
"componentEnabled": true
}
},
+ "toc": {
+ "Config": {
+ "actions": [
+ "show-description",
+ "zoom-to-extent",
+ "activate-children",
+ "deactivate-children",
+ "change-opacity",
+ "show-copyright",
+ "timeslider"
+ ]
+ }
+ },
"banner": {
"BannerWidget": {
"label": "Developer Network",
@@ -99,8 +112,40 @@
{
"id": "buchdrucker",
"title": "Gefährdung Buchdrucker",
- "type": "AGS_FEATURE",
- "url": "https://www.fovgis.bayern.de/arcgis/rest/services/baywis_wsm/borki_gef/FeatureServer/1"
+ "type": "AGS_DYNAMIC",
+ "url": "https://www.fovgis.bayern.de/arcgis/rest/services/baywis_wsm/borki_gef/MapServer",
+ "listMode": "hide-children",
+ "sublayers": [
+ {
+ "id": 1,
+ "visible": true
+ }
+ ],
+ "timeSlider": {
+ "timeExtent": {
+ "start": "2019-04-15T00:00Z",
+ "end": "2019-04-15T00:00Z"
+ },
+ "fullTimeExtent": {
+ "start": "2019-01-01T00:00Z",
+ "end": "2019-12-31T00:00Z"
+ },
+ "stops": {
+ "interval": {
+ "value": 1,
+ "unit": "weeks"
+ },
+ "timeExtent": {
+ "start": "2019-04-01T00:00Z",
+ "end": "2019-10-06T00:00Z"
+ }
+ },
+ "mode": "instant",
+ "loop": true,
+ "playRate": 1000,
+ "playOnStartup": true,
+ "timeVisible": false
+ }
},
{
"id": "kupferstecher",
@@ -229,7 +274,8 @@
"toolrules": {
"ToolActiveStateManager": {
"activateOnStartToolIds": [
- "timeSliderToggleTool",
+ // "timeSliderToggleTool",
+ "tocToggleTool",
"legendToggleTool"
]
}
diff --git a/src/main/js/bundles/dn_timeslider/README.md b/src/main/js/bundles/dn_timeslider/README.md
index 904dbf1..6e4b830 100644
--- a/src/main/js/bundles/dn_timeslider/README.md
+++ b/src/main/js/bundles/dn_timeslider/README.md
@@ -1,26 +1,40 @@
# dn_timeslider
-The Time Slider bundle allows the user to change the time extent of the map.
+The Time Slider bundle allows the user to change the time extent of the map or specific layers.
## Usage
**Requirement: map.apps 4.12.0**
-1. First you need to add the bundle dn_timeslider to your app.
-2. Then you need to configure it.
-
-To make the functions of this bundle available to the user, the following tool can be added to a toolset:
-
-| Tool ID | Component | Description |
-| -------------------- | -------------------- | ------------------------ |
-| timeSliderToggleTool | TimeSliderToggleTool | Show or hide the widget. |
+
+ - First you need to add the bundle dn_timeslider to your app.
+ - Then you need to configure it. This can be accomplished in two separate yet complementary ways:
+
+
+
## Configuration Reference
### Config
-#### Sample configuration
+#### Sample configuration for map Time Slider
```json
"Config": {
+ "timeExtent": {
+ "start": "2019-04-15T00:00Z",
+ "end": "2019-04-15T00:00Z"
+ },
"fullTimeExtent": {
"start": "2019-01-01T00:00Z",
"end": "2019-12-31T00:00Z"
@@ -44,6 +58,50 @@ To make the functions of this bundle available to the user, the following tool c
}
```
+#### Sample configuration for layer Time Slider
+```json
+"map-init": {
+ "Config": {
+ "map": {
+ "layers": [
+ {
+ "id": "buchdrucker",
+ "title": "Gefährdung Buchdrucker",
+ "type": "AGS_FEATURE",
+ "url": "https://www.fovgis.bayern.de/arcgis/rest/services/baywis_wsm/borki_gef/FeatureServer/1",
+ "timeSlider": {
+ "timeExtent": {
+ "start": "2019-04-15T00:00Z",
+ "end": "2019-04-15T00:00Z"
+ },
+ "fullTimeExtent": {
+ "start": "2019-01-01T00:00Z",
+ "end": "2019-12-31T00:00Z"
+ },
+ "viewTimeExtent": null,
+ "stops": {
+ "interval": {
+ "value": 1,
+ "unit": "weeks"
+ },
+ "timeExtent": {
+ "start": "2019-03-01T00:00Z",
+ "end": "2019-09-01T00:00Z"
+ }
+ },
+ "mode": "instant",
+ "loop": true,
+ "playRate": 1000,
+ "playOnStartup": true,
+ "timeVisible": false
+ }
+ }
+ ]
+ }
+ }
+}
+```
+
| Property | Type | Possible Values | Default | Description |
| -------------- | ------- | ---------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| timeExtent | Object | | | The initial temporal extent of the slider. More information is available in the [TimeSlider](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-TimeSlider-TimeSliderViewModel.html#timeExtent) documentation. |
@@ -56,6 +114,8 @@ To make the functions of this bundle available to the user, the following tool c
| playOnStartup | Boolean | ```true``` | ```false``` | ```false``` | When true, the time slider will play its animation on startup. |
| timeVisible | Boolean | ```true``` | ```false``` | ```false``` | Shows/hides time in the display. |
+
+
#### Configuration of fullTimeExtent
To configure this property you need to define a start and end date. To do this you can use [Moment.js-Strings](https://momentjs.com/docs/#/parsing/).
@@ -263,5 +323,5 @@ If you configure the stops over an interval or a number, you have the additional
### Configuration of labels
-To change the format and styling of the labels, you can inject a FormatFunction with the Interface `"dn_timeslider.LabelFormatFunction"`. It allows to change the visual representation of the labels inside of the TimeSlider.
+To change the format and styling of the labels, you can inject a FormatFunction with the Interface `"dn_timeslider.LabelFormatFunction"`. It allows to change the visual representation of the labels inside of the TimeSlider.
More information is available in the [TimeSlider::labelFormatFunction](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-TimeSlider.html#labelFormatFunction) documentation.
diff --git a/src/main/js/bundles/dn_timeslider/TimeSliderTocActionDefinitionFactory.ts b/src/main/js/bundles/dn_timeslider/TimeSliderTocActionDefinitionFactory.ts
new file mode 100644
index 0000000..ed8c113
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/TimeSliderTocActionDefinitionFactory.ts
@@ -0,0 +1,112 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import type { InjectedReference } from "apprt-core/InjectedReference";
+import ct_util from "ct/ui/desktop/util";
+import async from "apprt-core/async";
+import EsriDijit from "esri-widgets/EsriDijit";
+
+import { MessagesReference } from "./nls/bundle";
+import type TimeSliderWidgetController from "./TimeSliderWidgetController";
+import { ExtendedLayer } from "../../types/ExtendedLayer";
+
+export default class TimeSliderTocActionDefinitionFactory {
+ public delay = 1000;
+ public supportedIds: Array;
+
+ private Id = "timeslider";
+ private bundleContext: InjectedReference = undefined;
+ private timeExtentWatcher: InjectedReference = undefined;
+ private serviceRegistration: InjectedReference = undefined;
+ private _i18n: InjectedReference;
+ private _timeSliderWidgetController: TimeSliderWidgetController;
+
+ public activate(componentContext: InjectedReference): void {
+ this.bundleContext = componentContext.getBundleContext();
+ }
+
+ public constructor() {
+ this.supportedIds = [this.Id];
+ }
+
+ public createDefinitionById(id: string): any {
+ if (id !== this.Id) {
+ return;
+ }
+ const i18n = this._i18n.get();
+ const timeSliderWidgetController = this._timeSliderWidgetController;
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const that = this;
+
+ return {
+ id: "timeslider",
+ type: "button",
+ label: i18n.tocActionLabel,
+ icon: "icon-time-forward",
+
+ isVisibleForItem(tocItem: any) {
+ const ref = tocItem.ref;
+ return typeof ref.timeInfo !== "undefined" && ref.timeInfo !== null && !!ref.timeSlider;
+ },
+
+ trigger(tocItem: any) {
+ const layer = tocItem.ref;
+ const controller = timeSliderWidgetController;
+ const timeSliderProperties = tocItem.ref.timeSlider;
+
+ layer.visible = true;
+
+ if (layer.timeExtent) {
+ timeSliderProperties.timeExtent = layer.timeExtent;
+ timeSliderProperties.fullTimeExtent = layer.timeInfo.fullTimeExtent;
+ timeSliderProperties.stops = layer.stops;
+ }
+
+ const timeSliderWidget = controller.getWidget(timeSliderProperties);
+ that.timeExtentWatcher = timeSliderWidget.watch("timeExtent", (value) => {
+ layer.timeExtent = value;
+ });
+ this.supressLayerDefaults(layer, timeSliderProperties, timeSliderWidget);
+ const widget = new (EsriDijit as any)(timeSliderWidget);
+ const serviceProperties = {
+ "widgetRole": "layerTimeSliderWidget"
+ };
+ const interfaces = ["dijit.Widget"];
+ that.serviceRegistration = that.bundleContext.registerService(interfaces, widget, serviceProperties);
+
+ async(() => {
+ const window = ct_util.findEnclosingWindow(timeSliderWidget);
+ window.set("title", `${window.title} - ${layer.title}`);
+ window?.on("Hide", () => {
+ that.timeExtentWatcher.remove();
+ that.timeExtentWatcher = undefined;
+ });
+ }, that.delay);
+ },
+
+ supressLayerDefaults(layer: ExtendedLayer, props: InjectedReference>, widget: any) {
+ if (props) {
+ layer.timeInfo.fullTimeExtent = props.fullTimeExtent;
+ layer.stops = props.stops;
+ } else if (widget.fullTimeExtent) {
+ layer.timeInfo.fullTimeExtent = widget.fullTimeExtent;
+ layer.stops = widget.stops;
+ }
+ }
+ };
+ }
+
+}
diff --git a/src/main/js/bundles/dn_timeslider/TimeSliderWidgetController.js b/src/main/js/bundles/dn_timeslider/TimeSliderWidgetController.js
deleted file mode 100644
index 3d6ec20..0000000
--- a/src/main/js/bundles/dn_timeslider/TimeSliderWidgetController.js
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import TimeSlider from "esri/widgets/TimeSlider";
-import moment from "moment";
-
-export default class TimeSliderWidgetFactory {
-
- #timeSliderWidget = undefined;
- #timeExtentWatcher = undefined;
- #initialTimeExtent = undefined;
-
- /**
- * A function used to specify custom formatting and styling
- * @type __esri.DateLabelFormatter
- */
- #labelFormatFunction = undefined;
-
- activate() {
- this._getView().then((view) => {
- // check for viewTimeExtent property; if undefined don't set new view's timeExtent
- if (this._properties.viewTimeExtent) {
- view.timeExtent = this._getViewTimeExtent();
- }
- this.#initialTimeExtent = view.timeExtent;
- });
- }
-
- deactivate() {
- this._resetTimeExtent();
- this._destroyWidget();
- }
-
- /**
- * Sets formatter for min, max and extent labels of the TimeSlider
- * Injected via OSGI
- * @param {__esri.DateLabelFormatter} labelFormatFunction A function used to specify custom formatting and styling
- * @public
- */
- setLabelFormatFunction(labelFormatFunction) {
- this.#labelFormatFunction = labelFormatFunction;
- }
-
- /**
- * Gets called when the tool gets activated
- */
- onToolActivated() {
- this._getView().then((view) => {
- view.timeExtent = this.#timeSliderWidget.timeExtent;
- this._changeAllLayerTimeExtents(view.timeExtent);
- if (this._properties.playOnStartup) {
- this.#timeSliderWidget.play();
- }
- this.#timeExtentWatcher = this.#timeSliderWidget.watch("timeExtent", (value) => {
- this._changeAllLayerTimeExtents(value);
- });
- });
- }
-
- /**
- * Gets called when the tool gets deactivated
- */
- onToolDeactivated() {
- this.#timeSliderWidget.stop();
- this.#timeExtentWatcher.remove();
- this._resetAllLayerTimeExtents();
- this._resetTimeExtent();
- }
-
- /**
- * Returns the TimeSliderWidget
- *
- * @returns {TimeSlider}
- */
- getWidget() {
- const timeSliderProperties = this._getTimeSliderProperties();
- return this.#timeSliderWidget = new TimeSlider(timeSliderProperties);
- }
-
- _destroyWidget() {
- this.#timeSliderWidget.destroy();
- this.#timeSliderWidget = undefined;
- }
-
- /**
- * Generated the TimeSlider configuration
- *
- * @returns {*} TimeSlider configuration
- */
- _getTimeSliderProperties() {
- const properties = this._properties;
- const timeSliderProperties = {
- timeExtent: this._getInitialTimeExtent(),
- fullTimeExtent: this._getFullTimeExtent(),
- mode: properties.mode,
- loop: properties.loop,
- playRate: properties.playRate,
- timeVisible: properties.timeVisible
- };
- const stops = this._getStops();
- if (stops) {
- timeSliderProperties.stops = stops;
- }
-
- if (this.#labelFormatFunction) {
- // use formatter in widget
- timeSliderProperties.labelFormatFunction = this.#labelFormatFunction;
- }
-
- return timeSliderProperties;
- }
-
- /**
- * Generates the initial values for the TimeSlider
- *
- * @returns {Array} Values for the TimeSlider
- * @private
- */
- _getInitialTimeExtent() {
- const properties = this._properties;
- const timeExtent = properties.timeExtent;
-
- return this._getTimeExtent(timeExtent);
- }
-
- /**
- * Function used to access fullTimeExtent properties and call _getTimeExtent()
- *
- * @returns {{start, end}} An object with start and end date
- * @private
- */
- _getFullTimeExtent() {
- const properties = this._properties;
- const fullTimeExtent = properties.fullTimeExtent;
-
- return this._getTimeExtent(fullTimeExtent);
- }
-
- /**
- * Function used to access viewTimeExtent properties and call _getTimeExtent()
- *
- * @returns {{start, end}} An object with start and end date
- * @private
- */
- _getViewTimeExtent() {
- const properties = this._properties;
- const viewTimeExtent = properties.viewTimeExtent;
- return this._getTimeExtent(viewTimeExtent);
- }
-
- /**
- * Function used to pass start and end component of timeExtent seperatly to _constructMoment()
- *
- * @param {Object} referenceTimeExtent Object representing timeExtent; Contains start and end property
- * @returns {{start, end}} Object containing two dates, constructed using _constructMoment()
- * @private
- */
- _getTimeExtent(referenceTimeExtent) {
- const start = this._constructDate(referenceTimeExtent.start);
- const end = this._constructDate(referenceTimeExtent.end);
-
- return {
- start: start,
- end: end
- };
- }
-
- /**
- * Function used to construct a moment from properties defined in manifest.json/app.json
- * @param {Object} referenceMoment Moment properties extracted from referenceTimeExtent
- * @returns Moment constructed according to parameters
- * @private
- */
- _constructDate(referenceMoment) {
- let momentObj = moment.utc();
- // Case: Property is either "now", undefined or null
- if (referenceMoment === "now" || !referenceMoment) {
- // do nothing because moment object has already the current date
- } else if (Array.isArray(referenceMoment)) {
- referenceMoment.forEach((m, i) => {
- // Case: Property is an array and leading element is a string
- if (typeof m === 'string' && i === 0) {
- momentObj = moment(m).utc();
- if (momentObj && !momentObj.isValid()) {
- momentObj = moment().utc();
- console.warn("Invalid timeExtent definition. Use current time.");
- }
- } else {
- try {
- momentObj[m.method].apply(momentObj, m.args);
- } catch {
- console.warn("Invalid moment definition in timeExtent definition. Use current time.");
- }
- }
- });
- }
- // Case: Property is a string but not "now"
- else if (typeof referenceMoment === 'string' && referenceMoment !== "now") {
- momentObj = moment(referenceMoment).utc();
- if (momentObj && !momentObj.isValid()) {
- momentObj = moment.utc();
- console.warn("Invalid timeExtent definition. Use current time.");
- }
- }
-
- return momentObj.toDate();
- }
-
- /**
- * Generates stops values for the TimeSlider
- *
- * @returns Object that contains the stop configuration
- * @private
- */
- _getStops() {
- const properties = this._properties;
- const stopsProperties = properties.stops;
- const defaultStopCount = 10;
- let stops = null;
-
- // If stop properties are defined
- if (stopsProperties) {
- // Case: Stops are defined using dates
- if (stopsProperties.dates) {
- stops = {};
- const dates = [];
- let momentObj;
- stopsProperties.dates.forEach((dateString) => {
- momentObj = moment(dateString).utc();
- // Push valid dates
- if (momentObj?.isValid()) {
- dates.push(momentObj.toDate());
- }
- // Warn for invalid dates and skip date
- else {
- console.warn("Invalid date stop definition. Skip value.");
- }
- });
- stops.dates = dates;
- }
- // Check if stops are defined using moments and calculations
- else if (stopsProperties.moment) {
- stops = {};
- const dates = [];
- let momentObj = moment().utc();
- stopsProperties.moment.forEach((timeStop) => {
- // Check if leading timeStop element is a string
- if (typeof timeStop === 'string') {
- momentObj = moment(timeStop).utc();
- if (momentObj?.isValid()) {
- dates.push(momentObj.toDate());
- } else {
- momentObj = moment.utc();
- console.warn("Invalid stop definition at start of the array. Use current time.");
- }
- }
- // stop is an array
- else if (Array.isArray(timeStop)) {
- timeStop.forEach((time) => {
- try {
- momentObj[time.method].apply(momentObj, time.args);
- if (momentObj?.isValid()) {
- dates.push(momentObj.toDate());
- }
- } catch {
- console.warn("Invalid stop definition in moment array. Skip array entry.");
- }
- });
- }
- // stop is a single object
- else {
- try {
- momentObj[timeStop.method].apply(momentObj, timeStop.args);
- if (momentObj?.isValid()) {
- dates.push(momentObj.toDate());
- }
- } catch {
- console.warn("Invalid stop definition in moment array. Skip array entry.");
- }
- }
- });
- stops.dates = dates;
- }
- // Case: Stops are defined by count or interval
- else {
- // Case: Stops defined by count
- if (stopsProperties.count) {
- stops = {};
- stops.count = stopsProperties.count || defaultStopCount;
- }
- // Case: Stops defined by interval
- else if (stopsProperties.interval) {
- stops = {};
- stops.interval = {
- value: stopsProperties.interval.value || 1,
- unit: stopsProperties.interval.unit || "years"
- };
- }
- // Case: No definition provided. Use 10 stops instead
- else {
- stops = {};
- stops.count = defaultStopCount;
- }
- // Case: Stops defined by start and end time and interval/count
- if (stopsProperties.timeExtent && stops) {
- let start = null;
- let end = null;
- if (stopsProperties.timeExtent.start) {
- start = moment(stopsProperties.timeExtent.start);
- if (!start?.isValid()) {
- start = moment.utc();
- console.warn("No valid configuration for stop timeExtent start. Using current time.");
- }
- }
- if (stopsProperties.timeExtent.end) {
- end = moment(stopsProperties.timeExtent.end);
- if (end && end.isValid() === false) {
- end = moment.utc();
- console.warn("No valid configuration for stop timeExtent end. Using current time.");
- }
- }
- if (start && end) {
- stops.timeExtent = {
- start: start.toDate(),
- end: end.toDate()
- };
- }
- }
- }
- }
- return stops;
- }
-
- _changeAllLayerTimeExtents(timeExtent) {
- const mapWidgetModel = this._mapWidgetModel;
- const map = mapWidgetModel.map;
- const layers = map.layers;
- const flattenLayers = this._getFlattenLayers(layers);
- flattenLayers.forEach((layer) => {
- if (layer.useViewTime) {
- if (layer.timeExtent && !layer._initialTimeExtent) {
- layer._initialTimeExtent = layer.timeExtent;
- }
- layer.timeExtent = timeExtent;
- }
- });
- }
-
- _resetAllLayerTimeExtents() {
- const mapWidgetModel = this._mapWidgetModel;
- const map = mapWidgetModel.map;
- const layers = map.layers;
- const flattenLayers = this._getFlattenLayers(layers);
- flattenLayers.forEach((layer) => {
- layer.timeExtent = layer._initialTimeExtent;
- });
- }
-
- _getFlattenLayers(layers) {
- return layers.flatten(item => item.layers || item.sublayers);
- }
-
- /**
- * Converts a date string to a Date Object via moment
- *
- * @param config Date string
- * @returns {Date}
- * @private
- */
- _getDate(config) {
- return moment(config).toDate();
- }
-
- /**
- * Returns the current View of the MapWidgetModel
- *
- * @returns {Promise} The current View
- * @private
- */
- _getView() {
- const mapWidgetModel = this._mapWidgetModel;
- return new Promise((resolve) => {
- if (mapWidgetModel.view) {
- resolve(mapWidgetModel.view);
- } else {
- const watcher = mapWidgetModel.watch("view", ({ value: view }) => {
- watcher.remove();
- resolve(view);
- });
- }
- });
- }
-
- /**
- * Resets the initial TimeExtent value of the View
- *
- * @private
- */
- _resetTimeExtent() {
- this._getView().then((view) => {
- if (this.#initialTimeExtent) {
- view.timeExtent = this.#initialTimeExtent;
- }
- });
- }
-}
diff --git a/src/main/js/bundles/dn_timeslider/TimeSliderWidgetController.ts b/src/main/js/bundles/dn_timeslider/TimeSliderWidgetController.ts
new file mode 100644
index 0000000..217752b
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/TimeSliderWidgetController.ts
@@ -0,0 +1,290 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { InjectedReference } from "apprt-core/InjectedReference";
+import TimeSlider from "esri/widgets/TimeSlider";
+import moment from "moment";
+
+import { MapWidgetModel } from "map-widget/api";
+import { TimesliderProperties } from "../../types/TimesliderProperties";
+import TimeExtent from "esri/TimeExtent";
+
+export default class TimeSliderWidgetController {
+
+ private _properties: InjectedReference>;
+ private timeSliderWidget: any = undefined;
+ private initialTimeExtent: any = undefined;
+ private labelFormatFunction: any = undefined;
+ private _mapWidgetModel: InjectedReference;
+
+ public activate(): void {
+ const properties = this._properties;
+ this.getView().then((view: __esri.View) => {
+ if (this._properties.viewTimeExtent) {
+ view.timeExtent = this.getTimeExtentFromConfig(properties, "viewTimeExtent");
+ }
+ this.initialTimeExtent = view.timeExtent;
+ });
+ }
+
+ public setLabelFormatFunction(labelFormatFunction: __esri.DateLabelFormatter): void {
+ this.labelFormatFunction = labelFormatFunction;
+ }
+
+ public deactivate(): void {
+ this.resetTimeExtent();
+ this.destroyWidget();
+ }
+
+ public onToolActivated(): void {
+ this.getView().then((view: __esri.View) => {
+ view.timeExtent = this.timeSliderWidget.timeExtent;
+ if (this._properties.playOnStartup) {
+ this.timeSliderWidget.play();
+ }
+ });
+ }
+
+ public onToolDeactivated(): void {
+ this.timeSliderWidget.stop();
+ this.resetTimeExtent();
+ }
+
+ public getWidget(properties?: InjectedReference>): TimeSlider {
+ const timeSliderProperties = this.getTimeSliderProperties(properties || this._properties);
+ return this.timeSliderWidget = new TimeSlider(timeSliderProperties);
+ }
+
+ private destroyWidget(): void {
+ this.timeSliderWidget.destroy();
+ this.timeSliderWidget = undefined;
+ }
+
+ public getTimeSliderProperties(properties: InjectedReference>): TimesliderProperties {
+ const timeSliderProperties: TimesliderProperties = {
+ timeExtent: this.getTimeExtentFromConfig(properties, "timeExtent"),
+ fullTimeExtent: this.getTimeExtentFromConfig(properties, "fullTimeExtent"),
+ viewTimeExtent: this.getTimeExtentFromConfig(properties, "viewTimeExtent"),
+ labelFormatFunction: properties?.labelFormatFunction,
+ stops: properties.stops,
+ mode: properties.mode,
+ loop: properties.loop,
+ playRate: properties.playRate,
+ playOnStartup: properties.playOnStartup,
+ timeVisible: properties.timeVisible
+ };
+ const stops = this.getStops(properties);
+ if (stops) {
+ timeSliderProperties.stops = stops;
+ }
+
+ if (this.labelFormatFunction) {
+ timeSliderProperties.labelFormatFunction = this.labelFormatFunction;
+ }
+
+ return timeSliderProperties;
+ }
+
+ private getTimeExtentFromConfig(properties: InjectedReference>,
+ attribute: string): __esri.TimeExtent {
+ const timeExtent = properties[attribute];
+ if (timeExtent) {
+ return this.getTimeExtent(timeExtent);
+ } else {
+ return undefined;
+ }
+ }
+
+ private getTimeExtent(referenceTimeExtent: __esri.TimeExtent): __esri.TimeExtent {
+ if (moment.isDate(referenceTimeExtent.start) && moment.isDate(referenceTimeExtent.end)) {
+ return referenceTimeExtent;
+ } else {
+ return new TimeExtent({
+ start: this.constructDate(referenceTimeExtent.start),
+ end: this.constructDate(referenceTimeExtent.end)
+ });
+ }
+ }
+
+ private constructDate(dateRepresentation: string | Date): Date {
+ if (!dateRepresentation) {
+ return;
+ }
+
+ if (dateRepresentation === "now") {
+ return moment().toDate();
+ }
+
+ let momentObj = moment.utc();
+ if (Array.isArray(dateRepresentation)) {
+ dateRepresentation.forEach((date, index) => {
+ if (typeof date === 'string' && index === 0) {
+ momentObj = moment(date).utc();
+ if (momentObj && !momentObj.isValid()) {
+ momentObj = moment().utc();
+ console.warn("Invalid timeExtent definition. Use current time.");
+ }
+ } else {
+ try {
+ momentObj[date.method](...date.args);
+ } catch {
+ console.warn("Invalid moment definition in timeExtent definition. Use current time.");
+ }
+ }
+ });
+ }
+ else if (typeof dateRepresentation === 'string') {
+ momentObj = moment(dateRepresentation).utc();
+ if (momentObj && !momentObj.isValid()) {
+ momentObj = moment.utc();
+ console.warn("Invalid timeExtent definition. Use current time.");
+ }
+ }
+
+ return momentObj.toDate();
+ }
+
+ private getStops(properties: InjectedReference>): __esri.StopsByDates |
+ __esri.StopsByCount | __esri.StopsByInterval | undefined {
+ const stopsProperties = properties.stops;
+ const defaultStopCount = 10;
+ let stops = null;
+
+ if (!stopsProperties) {
+ return;
+ }
+
+ if (stopsProperties && !Object.keys(stopsProperties).length){
+ stops = {};
+ stops.count = defaultStopCount;
+ }
+
+ if (stopsProperties.dates) {
+ stops = {};
+ const dates = [];
+ let momentObj: moment;
+ stopsProperties.dates.forEach((dateString: string) => {
+ momentObj = moment(dateString).utc();
+ if (momentObj?.isValid()) {
+ dates.push(momentObj.toDate());
+ }
+ else {
+ console.warn("Invalid date stop definition. Skip value.");
+ }
+ });
+ stops.dates = dates;
+ }
+ else if (stopsProperties.count) {
+ stops = {};
+ stops.count = stopsProperties.count || defaultStopCount;
+ }
+ else if (stopsProperties.interval) {
+ stops = {};
+ stops.interval = {
+ value: stopsProperties.interval.value || 1,
+ unit: stopsProperties.interval.unit || "years"
+ };
+ }
+ else if (stopsProperties.moment) {
+ stops = {};
+ const dates = [];
+ let momentObj = moment().utc();
+
+ stopsProperties.moment.forEach((timeStop: string | Array | moment) => {
+ if (typeof timeStop === 'string') {
+ momentObj = moment(timeStop).utc();
+ if (momentObj?.isValid()) {
+ dates.push(momentObj.toDate());
+ } else {
+ momentObj = moment.utc();
+ console.warn("Invalid stop definition at start of the array. Use current time.");
+ }
+ }
+ else if (Array.isArray(timeStop)) {
+ timeStop.forEach((time) => {
+ try {
+ momentObj[time.method](...time.args);
+ if (momentObj?.isValid()) {
+ dates.push(momentObj.toDate());
+ }
+ } catch {
+ console.warn("Invalid stop definition in moment array. Skip array entry.");
+ }
+ });
+ }
+ else {
+ try {
+ momentObj[timeStop.method](...timeStop.args);
+ if (momentObj?.isValid()) {
+ dates.push(momentObj.toDate());
+ }
+ } catch {
+ console.warn("Invalid stop definition in moment array. Skip array entry.");
+ }
+ }
+ });
+ stops.dates = dates;
+ }
+ else {
+ if (stopsProperties.timeExtent && stops) {
+ let start = null;
+ let end = null;
+ if (stopsProperties.timeExtent.start) {
+ start = moment(stopsProperties.timeExtent.start);
+ if (!start?.isValid()) {
+ start = moment.utc();
+ console.warn("No valid configuration for stop timeExtent start. Using current time.");
+ }
+ }
+ if (stopsProperties.timeExtent.end) {
+ end = moment(stopsProperties.timeExtent.end);
+ if (end && end.isValid() === false) {
+ end = moment.utc();
+ console.warn("No valid configuration for stop timeExtent end. Using current time.");
+ }
+ }
+ if (start && end) {
+ stops.timeExtent = {
+ start: start.toDate(),
+ end: end.toDate()
+ };
+ }
+ }
+ }
+
+ return stops;
+ }
+
+ private getView(): Promise<__esri.View> {
+ const mapWidgetModel = this._mapWidgetModel;
+ return new Promise((resolve) => {
+ if (mapWidgetModel.view) {
+ resolve(mapWidgetModel.view);
+ } else {
+ const watcher = mapWidgetModel.watch("view", ({value: view}) => {
+ watcher.remove();
+ resolve(view);
+ });
+ }
+ });
+ }
+
+ private resetTimeExtent(): void {
+ this.getView().then((view: __esri.View) => {
+ view.timeExtent = this.initialTimeExtent;
+ });
+ }
+}
diff --git a/src/main/js/bundles/dn_timeslider/TimeSliderWidgetFactory.js b/src/main/js/bundles/dn_timeslider/TimeSliderWidgetFactory.js
deleted file mode 100644
index 232477a..0000000
--- a/src/main/js/bundles/dn_timeslider/TimeSliderWidgetFactory.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import EsriDijit from "esri-widgets/EsriDijit";
-import Binding from "apprt-binding/Binding";
-
-const _binding = Symbol("_binding");
-
-export default class TimeSliderWidgetFactory {
-
- deactivate() {
- this._deactivateBinding();
- }
-
- createInstance() {
- return this._getWidget();
- }
-
- _getWidget() {
- const timeSliderWidget = this._timeSliderWidgetController.getWidget();
- const mapWidgetModel = this._mapWidgetModel;
- const binding = this[_binding] = Binding.for(timeSliderWidget, mapWidgetModel)
- .syncToLeft("view")
- .enable()
- .syncToLeftNow();
-
- timeSliderWidget.own(binding);
-
- return new EsriDijit(timeSliderWidget);
- }
-
- _deactivateBinding() {
- this[_binding].unbind();
- this[_binding] = undefined;
- }
-}
diff --git a/src/main/js/bundles/dn_timeslider/TimeSliderWidgetFactory.ts b/src/main/js/bundles/dn_timeslider/TimeSliderWidgetFactory.ts
new file mode 100644
index 0000000..8854efe
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/TimeSliderWidgetFactory.ts
@@ -0,0 +1,54 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { InjectedReference } from "apprt-core/InjectedReference";
+import EsriDijit from "esri-widgets/EsriDijit";
+import Binding, { Bindable, Binding as BindingType } from 'apprt-binding/Binding';
+
+import { MapWidgetModel } from "map-widget/api";
+import type TimeSliderWidgetController from "./TimeSliderWidgetController";
+
+export default class TimeSliderWidgetFactory {
+
+ private binding: BindingType;
+ private _mapWidgetModel: InjectedReference;
+ private _timeSliderWidgetController: TimeSliderWidgetController;
+
+ public deactivate(): void {
+ this.deactivateBinding();
+ }
+
+ public createInstance(): any {
+ return this.getWidget();
+ }
+
+ private getWidget(): any {
+ const timeSliderWidget = this._timeSliderWidgetController.getWidget();
+ const mapWidgetModel = this._mapWidgetModel;
+
+ this.binding = Binding.for(timeSliderWidget as Bindable, mapWidgetModel)
+ .syncToLeft("view")
+ .enable()
+ .syncToLeftNow();
+
+ return new (EsriDijit as any)(timeSliderWidget);
+ }
+
+ private deactivateBinding(): void {
+ this.binding.unbind();
+ this.binding = undefined;
+ }
+}
diff --git a/src/main/js/bundles/dn_timeslider/main.js b/src/main/js/bundles/dn_timeslider/main.js
deleted file mode 100644
index 44be8f2..0000000
--- a/src/main/js/bundles/dn_timeslider/main.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import "dojo/i18n!./nls/bundle";
diff --git a/src/main/js/bundles/dn_timeslider/main.ts b/src/main/js/bundles/dn_timeslider/main.ts
new file mode 100644
index 0000000..159bfb9
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/main.ts
@@ -0,0 +1,17 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import "dojo/i18n!./nls/bundle";
diff --git a/src/main/js/bundles/dn_timeslider/manifest.json b/src/main/js/bundles/dn_timeslider/manifest.json
index 5396a23..9d06dd1 100644
--- a/src/main/js/bundles/dn_timeslider/manifest.json
+++ b/src/main/js/bundles/dn_timeslider/manifest.json
@@ -82,6 +82,62 @@
"l": 50
}
}
+ },
+ {
+ "widgetRole": "layerTimeSliderWidget",
+ "window": {
+ "title": "${windowTitle}",
+ "closable": true,
+ "maximizable": true,
+ "minimizeOnClose": false,
+ "resizable": true,
+ "marginBox": {
+ "w": 435,
+ "h": 230,
+ "l": 20,
+ "t": 125
+ },
+ "minSize": {
+ "w": 435,
+ "h": 230
+ },
+ "windowClass": "timeslider-window"
+ }
+ },
+ {
+ "widgetRole": "layerTimeSliderWidget",
+ "sublayout": [
+ "mobile_landscape",
+ "mobile_portrait"
+ ],
+ "window": {
+ "resizable": false,
+ "fixEdgesInViewport": {
+ "l": true,
+ "r": true
+ },
+ "marginBox": {
+ "h": 230,
+ "b": 42,
+ "r": 0,
+ "l": 0
+ }
+ }
+ },
+ {
+ "widgetRole": "layerTimeSliderWidget",
+ "sublayout": [
+ "mobile_landscape"
+ ],
+ "window": {
+ "resizable": false,
+ "marginBox": {
+ "h": 230,
+ "b": 0,
+ "r": 0,
+ "l": 50
+ }
+ }
}
],
"components": [
@@ -128,7 +184,7 @@
{
"name": "_mapWidgetModel",
"providing": "map-widget.MapWidgetModel"
- },
+},
{
"name": "labelFormatFunction",
"providing": "dn_timeslider.LabelFormatFunction",
@@ -160,6 +216,16 @@
"providing": "dn_timeslider.TimeSliderWidgetController"
}
]
+ },
+ {
+ "name": "TimeSliderTocActionDefinitionFactory",
+ "provides": "toc.ActionDefinitionFactory",
+ "references": [
+ {
+ "name": "_timeSliderWidgetController",
+ "providing": "dn_timeslider.TimeSliderWidgetController"
+ }
+ ]
}
]
}
diff --git a/src/main/js/bundles/dn_timeslider/module.js b/src/main/js/bundles/dn_timeslider/module.js
deleted file mode 100644
index b03456f..0000000
--- a/src/main/js/bundles/dn_timeslider/module.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import ".";
-import "./TimeSliderWidgetFactory";
-import "./TimeSliderWidgetController";
-import "ct/tools/Tool";
diff --git a/src/main/js/bundles/dn_timeslider/module.ts b/src/main/js/bundles/dn_timeslider/module.ts
new file mode 100644
index 0000000..5765c92
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/module.ts
@@ -0,0 +1,21 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import ".";
+import "./TimeSliderWidgetFactory";
+import "./TimeSliderWidgetController";
+import "./TimeSliderTocActionDefinitionFactory";
+import "ct/tools/Tool";
diff --git a/src/main/js/bundles/dn_timeslider/nls/bundle.js b/src/main/js/bundles/dn_timeslider/nls/bundle.js
deleted file mode 100644
index e501e1e..0000000
--- a/src/main/js/bundles/dn_timeslider/nls/bundle.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-module.exports = {
- root: {
- bundleName: "Time Slider",
- bundleDescription: "The Time Slider bundle allows the user to change the time extent of the map.",
- windowTitle: "Time Slider",
- tool: {
- title: "Time Slider",
- tooltip: "Time Slider"
- }
- },
- de: true
-};
diff --git a/src/main/js/bundles/dn_timeslider/nls/bundle.ts b/src/main/js/bundles/dn_timeslider/nls/bundle.ts
new file mode 100644
index 0000000..51fd0b0
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/nls/bundle.ts
@@ -0,0 +1,35 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+const i18n = {
+ root: {
+ bundleName: "Time Slider",
+ bundleDescription: "The Time Slider bundle allows the user to change the time extent of the map.",
+ windowTitle: "Time Slider",
+ tool: {
+ title: "Time Slider",
+ tooltip: "Time Slider"
+ },
+ tocActionLabel: "Time Slider for this Layer"
+ },
+ de: true
+};
+
+export type Messages = (typeof i18n)["root"];
+export interface MessagesReference {
+ get: () => Messages
+}
+export default i18n;
diff --git a/src/main/js/bundles/dn_timeslider/nls/de/bundle.js b/src/main/js/bundles/dn_timeslider/nls/de/bundle.js
deleted file mode 100644
index cad2724..0000000
--- a/src/main/js/bundles/dn_timeslider/nls/de/bundle.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-module.exports = {
- bundleName: "Time Slider",
- bundleDescription: "Das Time Slider Bundle erlaubt es dem Anwender den Zeitrahmen der Karte anzupassen.",
- windowTitle: "Time Slider",
- tool: {
- title: "Time Slider",
- tooltip: "Time Slider"
- }
-};
diff --git a/src/main/js/bundles/dn_timeslider/nls/de/bundle.ts b/src/main/js/bundles/dn_timeslider/nls/de/bundle.ts
new file mode 100644
index 0000000..578121e
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/nls/de/bundle.ts
@@ -0,0 +1,28 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Messages } from "../bundle";
+
+export default {
+ bundleName: "Time Slider",
+ bundleDescription: "Das Time Slider Bundle erlaubt es dem Anwender den Zeitrahmen der Karte anzupassen.",
+ windowTitle: "Time Slider",
+ tool: {
+ title: "Time Slider",
+ tooltip: "Time Slider"
+ },
+ tocActionLabel: "Time Slider für diesen Layer"
+} satisfies Messages;
diff --git a/src/main/js/bundles/dn_timeslider/tests/all.js b/src/main/js/bundles/dn_timeslider/tests/all.js
deleted file mode 100644
index e668b93..0000000
--- a/src/main/js/bundles/dn_timeslider/tests/all.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright (C) 2023 con terra GmbH (info@conterra.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import "./TimeSliderWidgetController.spec";
diff --git a/src/main/js/bundles/dn_timeslider/tests/all.ts b/src/main/js/bundles/dn_timeslider/tests/all.ts
new file mode 100644
index 0000000..b51bd00
--- /dev/null
+++ b/src/main/js/bundles/dn_timeslider/tests/all.ts
@@ -0,0 +1,17 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import "./TimeSliderWidgetController.spec";
diff --git a/src/main/js/types/ExtendedLayer.d.ts b/src/main/js/types/ExtendedLayer.d.ts
new file mode 100644
index 0000000..600c57e
--- /dev/null
+++ b/src/main/js/types/ExtendedLayer.d.ts
@@ -0,0 +1,20 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+export interface ExtendedLayer extends __esri.Layer {
+ timeInfo: __esri.TimeInfo,
+ stops: __esri.StopsByDates | __esri.StopsByCount | __esri.StopsByInterval
+}
diff --git a/src/main/js/types/TimesliderProperties.d.ts b/src/main/js/types/TimesliderProperties.d.ts
new file mode 100644
index 0000000..5367a89
--- /dev/null
+++ b/src/main/js/types/TimesliderProperties.d.ts
@@ -0,0 +1,28 @@
+///
+/// Copyright (C) 2023 con terra GmbH (info@conterra.de)
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+export interface TimesliderProperties {
+ timeExtent: __esri.TimeExtent,
+ fullTimeExtent: __esri.TimeExtent,
+ viewTimeExtent: __esri.TimeExtent,
+ labelFormatFunction: __esri.DateLabelFormatter,
+ stops: __esri.StopsByDates | __esri.StopsByCount | __esri.StopsByInterval
+ mode: "instant" | "time-window" | "cumulative-from-start" | "cumulative-from-end",
+ loop: boolean,
+ playRate: number,
+ playOnStartup: boolean,
+ timeVisible: boolean
+}