diff --git a/app/assets/stylesheets/content/work_packages/timelines/_slider.sass b/app/assets/stylesheets/content/work_packages/timelines/_slider.sass new file mode 100644 index 0000000000..41a9076b45 --- /dev/null +++ b/app/assets/stylesheets/content/work_packages/timelines/_slider.sass @@ -0,0 +1,46 @@ +.wp-timeline--slider-wrapper + position: absolute + bottom: 0 + z-index: 101 + + +// Slider +.noUi-horizontal.wp-timeline--slider + padding-right: 32px + + .noUi-handle + left: -1px + height: calc(1rem - 2px) + top: 0 + box-shadow: none + background: $gray + + &:before, &:after + height: calc(1rem - 6px) + top: 1px + background: $light-gray + + &:before + left: calc(50% - 1px) + &:after + left: calc(50% + 1px) + + .noUi-origin + right: -32px + + +// Scroll slider +.wp-timeline--slider + position: relative + height: 1rem + border-radius: none + + .ui-slider-handle + position: absolute + top: 0 + width: 1rem + height: 100% + cursor: default + touch-action: none + border-radius: none + background: $primary-color diff --git a/app/assets/stylesheets/content/work_packages/timelines/_timelines.sass b/app/assets/stylesheets/content/work_packages/timelines/_timelines.sass index a6f6da4e5b..fbd8ce044e 100644 --- a/app/assets/stylesheets/content/work_packages/timelines/_timelines.sass +++ b/app/assets/stylesheets/content/work_packages/timelines/_timelines.sass @@ -11,7 +11,7 @@ .wp-timeline-header-container.generic-table--sort-header-outer padding: 0 z-index: 100 - margin-left: -2px + margin-left: -5px height: $generic-table--header-height &:hover @@ -20,7 +20,7 @@ display: block .wp-timeline--scroll-wrapper - border-left: 2px solid $primary-color-dark + border-left: 5px solid $gray // The scroll wrapper is resized to span the entire table. // Without this, scroll is caught by the header. @@ -30,25 +30,4 @@ // Overflow must be hidden since the width of the column is fixed. // Since height is explicitly set, this is effectively // overflow-x: hidden / overflow-y: visible - overflow: hidden - -// Scroll slider container -// (To maintain border style) -.wp-timeline--slider-wrapper - border-left: 2px solid $primary-color-dark - -// Scroll slider -.wp-timeline--slider - position: relative - height: 1rem - border-radius: none - - .ui-slider-handle - position: absolute - top: 0 - width: 1rem - height: 100% - cursor: default - touch-action: none - border-radius: none - background: $primary-color \ No newline at end of file + overflow: hidden \ No newline at end of file diff --git a/app/assets/stylesheets/open_project_global/_variables.sass b/app/assets/stylesheets/open_project_global/_variables.sass index b6db85d8bf..d7493fe415 100644 --- a/app/assets/stylesheets/open_project_global/_variables.sass +++ b/app/assets/stylesheets/open_project_global/_variables.sass @@ -223,5 +223,5 @@ $table-header-shadow-color: #DDDDDD !default $generic-table--font-size: 0.875rem $generic-table--header-font-size: 0.875rem -$generic-table--header-height: 60px +$generic-table--header-height: 45px $generic-table--footer-height: 34px \ No newline at end of file diff --git a/frontend/app/components/wp-table/timeline/wp-timeline.header.ts b/frontend/app/components/wp-table/timeline/wp-timeline.header.ts index 7578b6c3c6..5762953df9 100644 --- a/frontend/app/components/wp-table/timeline/wp-timeline.header.ts +++ b/frontend/app/components/wp-table/timeline/wp-timeline.header.ts @@ -57,6 +57,7 @@ export class WpTimelineHeader { /** UI Scrollbar */ private scrollBar: HTMLElement; + private scrollWrapper: JQuery; private scrollBarHandle: JQuery; private scrollBarOrigin: JQuery; @@ -91,7 +92,8 @@ export class WpTimelineHeader { } setupScrollbar() { - this.scrollBar = this.outerHeader.find('.wp-timeline--slider')[0]; + this.scrollWrapper = jQuery('.wp-timeline--slider-wrapper'); + this.scrollBar = this.scrollWrapper.find('.wp-timeline--slider')[0]; noUiSlider.create(this.scrollBar, { start: [0], range: { @@ -106,21 +108,10 @@ export class WpTimelineHeader { let value = values[0]; this.wpTimeline.viewParameterSettings.scrollOffsetInDays = -value; this.wpTimeline.refreshScrollOnly(); - - //this.recalculateScrollBarLeftMargin(value); }); - // this.scrollBar.slider({ - // min: 0, - // slide: (evt, ui) => { - // this.wpTimeline.viewParameterSettings.scrollOffsetInDays = -ui.value; - // this.wpTimeline.refreshScrollOnly();≥ - - // this.recalculateScrollBarLeftMargin(ui.value); - // } - // }); - - this.scrollBarHandle = this.outerHeader.find('.noUi-handle'); - this.scrollBarOrigin = this.outerHeader.find('.noUi-origin'); + + this.scrollBarHandle = this.scrollWrapper.find('.noUi-handle'); + this.scrollBarOrigin = this.scrollWrapper.find('.noUi-origin'); } // noUiSlider doesn't extend the HTMLElement interface @@ -130,6 +121,9 @@ export class WpTimelineHeader { } private updateScrollbar(vp: TimelineViewParameters) { + // Update the scollbar to match the current width + this.scrollWrapper.css('width', this.outerHeader.width() + 'px'); + let maxWidth = this.scrollBar.offsetWidth, daysDisplayed = Math.min(vp.maxSteps, Math.floor(maxWidth / vp.pixelPerDay)), newMax = Math.max(vp.maxSteps - daysDisplayed, 1), diff --git a/frontend/app/components/wp-table/wp-table.directive.html b/frontend/app/components/wp-table/wp-table.directive.html index dbe98ccdc3..16f000f441 100644 --- a/frontend/app/components/wp-table/wp-table.directive.html +++ b/frontend/app/components/wp-table/wp-table.directive.html @@ -42,10 +42,10 @@ +
+
+
-
-
-
diff --git a/frontend/typings/globals/nouislider/index.d.ts b/frontend/typings/globals/nouislider/index.d.ts new file mode 100644 index 0000000000..ebb697b136 --- /dev/null +++ b/frontend/typings/globals/nouislider/index.d.ts @@ -0,0 +1,176 @@ +// Generated by typings +// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/92b2c03c297b8c5c7c2c7e169134da61f8abee6a/nouislider/nouislider.d.ts +declare namespace noUiSlider { + /** + * To create a slider, call noUiSlider.create() with an element and your options. + */ + function create(target: HTMLElement, options: Options): void; + + interface Options { + /** + * The start option sets the number of handles and their start positions, relative to range. + */ + start: number | number[] | number[][]; + /** + * The connect setting can be used to control the bar between the handles, + * or the edges of the slider. Use "lower" to connect to the lower side, + * or "upper" to connect to the upper side. Setting true sets the bar between the handles. + */ + range: Object; + /** + * noUiSlider offers several ways to handle user interaction. + * The range can be set to drag, and handles can move to taps. + * All these effects are optional, and can be enable by adding their keyword to the behaviour option. + * This option accepts a "-" separated list of "drag", "tap", "fixed", "snap" or "none". + */ + connect?: string | boolean; + /** + * When using two handles, the minimum distance between the handles can be set using the margin option. + * The margin value is relative to the value set in 'range'. + * This option is only available on standard linear sliders. + */ + margin?: number; + /** + * The limit option is the oposite of the margin option, + * limiting the maximum distance between two handles. + * As with the margin option, the limit option can only be used on linear sliders. + */ + limit?: number; + /** + * By default, the slider slides fluently. + * In order to make the handles jump between intervals, you can use this option. + * The step option is relative to the values provided to range. + */ + step?: number; + /** + * The orientation setting can be used to set the slider to "vertical" or "horizontal". + * Set dimensions! Vertical sliders don't assume a default height, so you'll need to set one. + * You can use any unit you want, including % or px. + */ + orientation?: string; + /** + * By default the sliders are top-to-bottom and left-to-right, + * but you can change this using the direction option, + * which decides where the upper side of the slider is. + */ + direction?: string; + /** + * Set the animate option to false to prevent the slider from animating to a new value with when calling .val(). + */ + animate?: boolean; + /** + * All values on the slider are part of a range. The range has a minimum and maximum value. + */ + behaviour?: string; + /** + * To format the slider output, noUiSlider offers a format option. + * Simply specify to and from functions to encode and decode the values. + * See manual formatting to the right for usage information. + * By default, noUiSlider will format output with 2 decimals. + */ + format?: Object | ((...args:any[]) => any); + + /** + * Allows you to generate points along the slider. + */ + pips?: PipsOptions; + } + + interface PipsOptions { + /** + * The 'range' mode uses the slider range to determine where the pips should be. A pip is generated for every percentage specified. + * + * The 'steps', like 'range', uses the slider range. In steps mode, a pip is generated for every step. + * The 'filter' option can be used to filter the generated pips from the 'steps' options' + * The filter function must return 0 (no value), 1 (large value) or 2 (small value). + * + * In 'positions' mode, pips are generated at percentage-based positions on the slider. + * Optionally, the stepped option can be set to true to match the pips to the slider steps. + * + * The 'count' mode can be used to generate a fixed number of pips. As with positions mode, the stepped option can be used. + * + * The 'values' mode is similar to positions, but it accepts values instead of percentages. The stepped option can be used for this mode. + * + */ + mode: string; // "range" | "steps" | "positions" | "count" | "values" + /** + * Range Mode: percentage for range mode + * Step Mode: step number for steps + * Positions Mode: percentage-based positions on the slider + * Count Mode: positions between pips + */ + density?: number; + /** + * Step Mode: The filter option can be used to filter the generated pips. + * The filter function must return 0 (no value), 1 (large value) or 2 (small value). + */ + filter?: (...args: any[]) => PipFilterResult; + /** + * format for step mode + * see noUiSlider format + */ + format?: Object; + /** + * + * values for positions and values mode + * number pips for count mode + */ + values?: number | number[]; + /** + * stepped option for positions, values and count mode + */ + stepped?: boolean; + } + + const enum PipFilterResult { + NoValue, + LargeValue, + SmallValue, + } + + interface Callback { + /** + * Array for both one-handle and two-handle sliders. It contains the current slider values, + * with formatting applied. + */ + (values: any[], handle: number, unencoded: number): void + } + + + interface noUiSlider { + /** + * Bind event to the slider. + */ + on(eventName: string, callback: Callback): void; + /** + * Unbind event to the slider. + */ + off(eventName: string): void; + /** + * Destroy's the slider. + */ + destroy(): void; + + /** + * To get the current slider value. For one-handle sliders, calling .get() will return the value. + * For two-handle sliders, an array[value, value] will be returned. + */ + get(): number | number[]; + /** + * noUiSlider will keep your values within the slider range, which saves you a bunch of validation. + * If you have configured the slider to use one handle, you can change the current value by passing + * a number to the .set() method. If you have two handles, pass an array. One-handled sliders + * will also accept arrays. Within an array, you can set one position to null + * if you want to leave a handle unchanged. + */ + set(value: number | number[]): void; + } + + interface Instance extends HTMLElement { + noUiSlider: noUiSlider + } +} + +declare module "nouislider" { + export = noUiSlider; +} diff --git a/frontend/typings/globals/nouislider/typings.json b/frontend/typings/globals/nouislider/typings.json new file mode 100644 index 0000000000..7dcb5573cd --- /dev/null +++ b/frontend/typings/globals/nouislider/typings.json @@ -0,0 +1,8 @@ +{ + "resolution": "main", + "tree": { + "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/92b2c03c297b8c5c7c2c7e169134da61f8abee6a/nouislider/nouislider.d.ts", + "raw": "registry:dt/nouislider#8.0.2+20160412140110", + "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/92b2c03c297b8c5c7c2c7e169134da61f8abee6a/nouislider/nouislider.d.ts" + } +}