From 9e97eba519b625af2e27219a07b3dab0c79560bc Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 6 Dec 2022 03:43:54 +0100 Subject: [PATCH] Add 'scrollIntoView' to baseUIElement, autoscroll questions into view when appropriate --- UI/BaseUIElement.ts | 30 ++++++++++++++++++++++++++ UI/Popup/EditableTagRendering.ts | 36 ++++++++++++++++++++------------ UI/Popup/QuestionBox.ts | 8 +++++++ Utils.ts | 17 +++++++++++++++ 4 files changed, 78 insertions(+), 13 deletions(-) diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 40feef7bf..dbf73e57e 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -3,6 +3,8 @@ * * Assumes a read-only configuration, so it has no 'ListenTo' */ +import {Utils} from "../Utils"; + export default abstract class BaseUIElement { protected _constructedHtmlElement: HTMLElement protected isDestroyed = false @@ -41,6 +43,34 @@ export default abstract class BaseUIElement { this._constructedHtmlElement?.scrollTo(0, 0) } + public ScrollIntoView(options?: { + onlyIfPartiallyHidden?: boolean + }) { + if(this._constructedHtmlElement === undefined){ + return + } + let alignToTop = true; + if(options?.onlyIfPartiallyHidden){ + // Is the element completely in the view? + const parentRect = Utils.findParentWithScrolling(this._constructedHtmlElement.parentElement).getBoundingClientRect(); + const elementRect = this._constructedHtmlElement.getBoundingClientRect(); + + // Check if the element is within the vertical bounds of the parent element + const topIsVisible = elementRect.top >= parentRect.top + const bottomIsVisible = elementRect.bottom <= parentRect.bottom + const inView = topIsVisible && bottomIsVisible ; + if(inView){ + return + } + if(topIsVisible){ + alignToTop = false + } + } + this._constructedHtmlElement?.scrollIntoView({ + behavior: "smooth", + block: "start" + }) + } /** * Adds all the relevant classes, space separated */ diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index ec119f668..177e144b8 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -1,16 +1,15 @@ -import { UIEventSource } from "../../Logic/UIEventSource" +import {UIEventSource} from "../../Logic/UIEventSource" import TagRenderingQuestion from "./TagRenderingQuestion" import Translations from "../i18n/Translations" import Combine from "../Base/Combine" import TagRenderingAnswer from "./TagRenderingAnswer" -import Svg from "../../Svg" import Toggle from "../Input/Toggle" import BaseUIElement from "../BaseUIElement" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" -import { Unit } from "../../Models/Unit" +import {Unit} from "../../Models/Unit" import Lazy from "../Base/Lazy" -import { FixedUiElement } from "../Base/FixedUiElement" -import { EditButton } from "./SaveButton" +import {FixedUiElement} from "../Base/FixedUiElement" +import {EditButton} from "./SaveButton" export default class EditableTagRendering extends Toggle { constructor( @@ -31,10 +30,9 @@ export default class EditableTagRendering extends Toggle { configuration.IsKnown(tags) && (configuration?.condition?.matchesProperties(tags) ?? true) ) - + const editMode = options.editMode ?? new UIEventSource(false) super( new Lazy(() => { - const editMode = options.editMode ?? new UIEventSource(false) let rendering = EditableTagRendering.CreateRendering( state, tags, @@ -54,6 +52,13 @@ export default class EditableTagRendering extends Toggle { undefined, renderingIsShown ) + const self = this; + editMode.addCallback(editing => { + if(editing){ + console.log("Scrolling etr into view") + self.ScrollIntoView() + } + }) } private static CreateRendering( @@ -69,12 +74,6 @@ export default class EditableTagRendering extends Toggle { if (configuration.question !== undefined && state?.featureSwitchUserbadge?.data) { // We have a question and editing is enabled - const answerWithEditButton = new Combine([ - answer, - new EditButton(state.osmConnection, () => { - editMode.setData(true) - }), - ]).SetClass("flex justify-between w-full") const question = new Lazy( () => @@ -92,7 +91,18 @@ export default class EditableTagRendering extends Toggle { }) ) + const answerWithEditButton = new Combine([ + answer, + new EditButton(state.osmConnection, () => { + editMode.setData(true) + question.ScrollIntoView({ + onlyIfPartiallyHidden:true + }) + + }), + ]).SetClass("flex justify-between w-full") rendering = new Toggle(question, answerWithEditButton, editMode) + } return rendering } diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index 0a5d324ef..30d48fd67 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -33,6 +33,8 @@ export default class QuestionBox extends VariableUiElement { .filter((tr) => tr.question !== undefined) .filter((tr) => tr.question !== null) + let focus: () => void = () => {}; + const tagRenderingQuestions = tagRenderings.map( (tagRendering, i) => new Lazy( @@ -42,6 +44,7 @@ export default class QuestionBox extends VariableUiElement { afterSave: () => { // We save and indicate progress by pinging and recalculating skippedQuestions.ping() + focus() }, cancelButton: Translations.t.general.skip .Clone() @@ -49,6 +52,8 @@ export default class QuestionBox extends VariableUiElement { .onClick(() => { skippedQuestions.data.push(i) skippedQuestions.ping() + focus() + }), }) ) @@ -136,5 +141,8 @@ export default class QuestionBox extends VariableUiElement { this.skippedQuestions = skippedQuestions this.restingQuestions = questionsToAsk + focus = () => this.ScrollIntoView({ + onlyIfPartiallyHidden: true + }) } } diff --git a/Utils.ts b/Utils.ts index f708943c3..3fa4c6fb1 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,4 +1,5 @@ import * as colors from "./assets/colors.json" +import HTML = Mocha.reporters.HTML; export class Utils { /** @@ -1221,4 +1222,20 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be d.setUTCMilliseconds(0) d.setUTCMinutes(0) } + + public static findParentWithScrolling(element: HTMLElement): HTMLElement { + // Check if the element itself has scrolling + if (element.scrollHeight > element.clientHeight) { + return element; + } + + // If the element does not have scrolling, check if it has a parent element + if (!element.parentElement) { + return null; + } + + // If the element has a parent, repeat the process for the parent element + return Utils.findParentWithScrolling(element.parentElement); + } + }