diff options
Diffstat (limited to 'src/static/js/client.js')
-rw-r--r-- | src/static/js/client.js | 259 |
1 files changed, 130 insertions, 129 deletions
diff --git a/src/static/js/client.js b/src/static/js/client.js index 935a9d87..21c3911a 100644 --- a/src/static/js/client.js +++ b/src/static/js/client.js @@ -1271,6 +1271,11 @@ const hoverableTooltipInfo = initInfo('hoverableTooltipInfo', { // from causing the current tooltip to be hidden. currentTouchIdentifiers: new Set(), touchIdentifiersBanishedByScrolling: new Set(), + + // This is a two-item array that tracks the direction we've already + // dynamically placed the current tooltip. If we *reposition* the tooltip + // (because its dimensions changed), we'll try to follow this anchor first. + dynamicTooltipAnchorDirection: null, }, event: { @@ -1731,6 +1736,8 @@ function hideCurrentlyShownTooltip(intendingToReplace = false) { state.currentlyShownTooltip = null; state.currentlyActiveHoverable = null; + state.dynamicTooltipAnchorDirection = null; + // Set this for one tick of the event cycle. state.tooltipWasJustHidden = true; setTimeout(() => { @@ -1758,6 +1765,11 @@ function showTooltipFromHoverable(hoverable) { positionTooltipFromHoverableWithBrains(hoverable); + // After a tooltip is shown, if we *didn't* specify an anchor, + // assume it was shown in its default position - generally presented + // as down and to the right. Successive repositioning will base on this. + state.dynamicTooltipAnchorDirection ??= ['down', 'right']; + cssProp(tooltip, 'display', 'block'); tooltip.inert = false; @@ -1792,10 +1804,23 @@ function peekTooltipClientRect(tooltip) { } } +function repositionCurrentTooltip() { + const {state} = hoverableTooltipInfo; + const {currentlyActiveHoverable} = state; + + if (!currentlyActiveHoverable) { + throw new Error(`No hoverable active to reposition tooltip from`); + } + + positionTooltipFromHoverableWithBrains(currentlyActiveHoverable); +} + function positionTooltipFromHoverableWithBrains(hoverable) { const {state} = hoverableTooltipInfo; const {tooltip} = state.registeredHoverables.get(hoverable); + const anchorDirection = state.dynamicTooltipAnchorDirection; + // Reset before doing anything else. We're going to adapt to // its natural placement, adjusted by CSS, which otherwise // could be obscured by a placement we've previously provided. @@ -1817,23 +1842,42 @@ function positionTooltipFromHoverableWithBrains(hoverable) { return; } - let selectedRect = null; - for (let i = 0; i < numBaselineRects; i++) { - selectedRect = opportunities.right.down[i]; - if (selectedRect) break; + const tryDirection = (dir1, dir2, i) => { + selectedRect = opportunities[dir1][dir2][i]; + return !!selectedRect; + }; - selectedRect = opportunities.left.down[i]; - if (selectedRect) break; + let selectedRect = null; + selectRect: { + if (anchorDirection) { + for (let i = 0; i < numBaselineRects; i++) { + if (tryDirection(...anchorDirection, i)) { + break selectRect; + } + } + } - selectedRect = opportunities.right.up[i]; - if (selectedRect) break; + for (let i = 0; i < numBaselineRects; i++) { + for (const [dir1, dir2] of [ + ['right', 'down'], + ['left', 'down'], + ['right', 'up'], + ['left', 'up'], + ['down', 'right'], + ['down', 'left'], + ['up', 'right'], + ['up', 'left'], + ]) { + if (tryDirection(dir1, dir2, i)) { + state.dynamicTooltipAnchorDirection = [dir1, dir2]; + break selectRect; + } + } + } - selectedRect = opportunities.left.up[i]; - if (selectedRect) break; + selectedRect = baselineRect; } - selectedRect ??= baselineRect; - positionTooltip(tooltip, selectedRect.x, selectedRect.y); } @@ -1929,18 +1973,18 @@ function getTooltipFromHoverablePlacementOpportunityAreas(hoverable) { const neededVerticalOverlap = 30; const neededHorizontalOverlap = 30; + const upTopDown = + WikiRect.beneath( + hoverableRect.top + neededVerticalOverlap - tooltipRect.height); + + const downBottomUp = + WikiRect.above( + hoverableRect.bottom - neededVerticalOverlap + tooltipRect.height); + // Please don't ask us to make this but horizontal? const prepareVerticalOrientationRects = (regionRects) => { const orientations = {}; - const upTopDown = - WikiRect.beneath( - hoverableRect.top + neededVerticalOverlap - tooltipRect.height); - - const downBottomUp = - WikiRect.above( - hoverableRect.bottom - neededVerticalOverlap + tooltipRect.height); - const orientHorizontally = (rect, i) => { if (!rect) return null; @@ -1996,9 +2040,67 @@ function getTooltipFromHoverablePlacementOpportunityAreas(hoverable) { return orientations; }; + const rightRightLeft = + WikiRect.leftOf( + hoverableRect.left - neededHorizontalOverlap + tooltipRect.width); + + const leftLeftRight = + WikiRect.rightOf( + hoverableRect.left + neededHorizontalOverlap - tooltipRect.width); + + // Oops. + const prepareHorizontalOrientationRects = (regionRects) => { + const orientations = {}; + + const orientVertically = (rect, i) => { + if (!rect) return null; + + const regionRect = regionRects[i]; + + if (regionRect.height > 0) { + return rect; + } else { + return WikiRect.fromRect({ + x: rect.x, + y: regionRect.bottom - tooltipRect.height, + width: rect.width, + height: rect.height, + }); + } + }; + + orientations.left = + regionRects + .map(rect => rect?.intersectionWith(leftLeftRight)) + .map(orientVertically) + .map(keepIfFits); + + orientations.right = + regionRects + .map(rect => rect?.intersectionWith(rightRightLeft)) + .map(rect => + (rect + ? rect.intersectionWith(WikiRect.fromRect({ + x: rect.right - tooltipRect.width, + y: rect.y, + width: rect.width, + height: tooltipRect.height, + })) + : null)) + .map(orientVertically) + .map(keepIfFits); + + // No analogous center because we don't actually use + // center alignment... + + return orientations; + }; + const orientationRects = { left: prepareVerticalOrientationRects(regionRects.left), right: prepareVerticalOrientationRects(regionRects.right), + down: prepareHorizontalOrientationRects(regionRects.bottom), + up: prepareHorizontalOrientationRects(regionRects.top), }; return { @@ -3106,114 +3208,6 @@ clientSteps.getPageReferences.push(getAdditionalNamesBoxReferences); clientSteps.addInternalListeners.push(addAdditionalNamesBoxInternalListeners); clientSteps.addPageListeners.push(addAdditionalNamesBoxListeners); -// Scoped chronology links -------------------------------- - -const scopedChronologyLinksInfo = initInfo('scopedChronologyLinksInfo', { - switcher: null, - containers: null, - switcherLinks: null, - modes: null, - - session: { - selectedMode: 'wiki', - }, -}); - -function getScopedChronologyLinksReferences() { - const info = scopedChronologyLinksInfo; - - info.switcher = - document.querySelector('.scoped-chronology-switcher'); - - if (!info.switcher) { - return; - } - - info.containers = - Array.from(info.switcher.querySelectorAll(':scope > div')); - - info.switcherLinks = - Array.from(info.switcher.querySelectorAll('.switcher-link')); - - info.modes = - info.containers - .map(container => - Array.from(container.classList) - .find(className => className.startsWith('scope-')) - .slice('scope-'.length)); -} - -function addScopedChronologyLinksPageHandlers() { - const info = scopedChronologyLinksInfo; - const {session} = scopedChronologyLinksInfo; - - if (!info.switcher) { - return; - } - - for (const [index, { - container: currentContainer, - switcherLink: currentSwitcherLink, - }] of stitchArrays({ - container: info.containers, - switcherLink: info.switcherLinks, - }).entries()) { - const nextContainer = - atOffset(info.containers, index, +1, {wrap: true}); - - const nextSwitcherLink = - atOffset(info.switcherLinks, index, +1, {wrap: true}); - - const nextMode = - atOffset(info.modes, index, +1, {wrap: true}); - - currentSwitcherLink.addEventListener('click', domEvent => { - domEvent.preventDefault(); - - cssProp(currentContainer, 'display', 'none'); - cssProp(currentSwitcherLink, 'display', 'none'); - cssProp(nextContainer, 'display', 'block'); - cssProp(nextSwitcherLink, 'display', 'inline'); - - session.selectedMode = nextMode; - }); - } -} - -function mutateScopedChronologyLinksContent() { - const info = scopedChronologyLinksInfo; - - if (!info.switcher) { - return; - } - - const {selectedMode} = info.session; - - if (info.modes.includes(selectedMode)) { - const selectedIndex = info.modes.indexOf(selectedMode); - - for (const [index, { - container, - switcherLink, - }] of stitchArrays({ - container: info.containers, - switcherLink: info.switcherLinks, - }).entries()) { - if (index === selectedIndex) { - cssProp(container, 'display', 'block'); - cssProp(switcherLink, 'display', 'inline'); - } else { - cssProp(container, 'display', 'none'); - cssProp(switcherLink, 'display', 'none'); - } - } - } -} - -clientSteps.getPageReferences.push(getScopedChronologyLinksReferences); -clientSteps.mutatePageContent.push(mutateScopedChronologyLinksContent); -clientSteps.addPageListeners.push(addScopedChronologyLinksPageHandlers); - // Group contributions table ------------------------------ // TODO: Update to clientSteps style. @@ -3363,7 +3357,7 @@ function getArtistExternalLinkTooltipPageReferences() { const info = artistExternalLinkTooltipInfo; info.tooltips = - Array.from(document.getElementsByClassName('icons-tooltip')); + Array.from(document.getElementsByClassName('contribution-tooltip')); info.tooltipRows = info.tooltips.map(tooltip => @@ -3507,6 +3501,8 @@ function showArtistExternalLinkTooltipInfo() { for (const tooltip of info.tooltips) { tooltip.classList.add('show-info'); } + + repositionCurrentTooltip(); } function hideArtistExternalLinkTooltipInfo() { @@ -3831,6 +3827,7 @@ const sidebarSearchInfo = initInfo('sidebarSearchInfo', { searchSidebarColumn: null, searchBox: null, + searchLabel: null, searchInput: null, progressRule: null, @@ -3919,6 +3916,9 @@ function getSidebarSearchReferences() { return; } + info.searchLabel = + info.searchBox.querySelector('.wiki-search-label'); + info.searchInput = info.searchBox.querySelector('.wiki-search-input'); @@ -4360,6 +4360,7 @@ function showSidebarSearchFailed() { cssProp(info.failedRule, 'display', null); cssProp(info.failedContainer, 'display', null); + info.searchLabel.classList.add('disabled'); info.searchInput.disabled = true; if (state.stoppedTypingTimeout) { |