Skip to content

Implement spy mode#1651

Merged
jumpinjackie merged 10 commits into
masterfrom
feature/spy-mode
May 21, 2026
Merged

Implement spy mode#1651
jumpinjackie merged 10 commits into
masterfrom
feature/spy-mode

Conversation

@jumpinjackie
Copy link
Copy Markdown
Owner

This pull request refactors and generalizes the map comparison feature, moving from a swipe-specific implementation to a more extensible model that supports multiple comparison modes (such as swipe and spy). The changes update type definitions, actions, and parsing logic to use the new terminology and data structures, making it easier to add or manage new comparison renderers in the future.

Generalization of map comparison:

  • Introduced a new ComparisonMode type ("none" | "swipe" | "spy") and a generalized IComparisonPair interface to replace the old swipe-specific IMapSwipePair, with type aliases for backward compatibility. ([[1]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-62eba76fe6cadaa56e99795f9b9fd052fa75b11f8d2a22bdcb0519eb43f723dcL1893-R1906), [[2]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-62eba76fe6cadaa56e99795f9b9fd052fa75b11f8d2a22bdcb0519eb43f723dcR1938-R1939))
  • Updated the configuration state and action payloads to use comparisonPairs and comparisonMode, with compatibility fields for mapSwipePairs and swipeActive. Added new state fields for lastComparisonMode and spyCursorRadius. ([[1]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-62eba76fe6cadaa56e99795f9b9fd052fa75b11f8d2a22bdcb0519eb43f723dcL2113-R2152), [[2]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-62eba76fe6cadaa56e99795f9b9fd052fa75b11f8d2a22bdcb0519eb43f723dcR2161-R2167), [3]

Action and reducer updates:

  • Replaced swipe-specific Redux actions with generalized comparison actions: ISetComparisonModeAction, ISetSwipePositionAction, and ISetSpyCursorRadiusAction. Updated action creators accordingly and provided compatibility aliases. ([[1]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-82b4c79ae83862ba64e8edb0ab72b2dbcf311d479ed187addc28af83d23bbb63L997-R1030), [[2]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-82b4c79ae83862ba64e8edb0ab72b2dbcf311d479ed187addc28af83d23bbb63L1083-R1098), [[3]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-cb3f3831b3c8a6045b000d641abc24af41f44da38c58f7734d3024b750a94d30L50-R52), [[4]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-cb3f3831b3c8a6045b000d641abc24af41f44da38c58f7734d3024b750a94d30L966-R1027))

Application definition and parsing:

  • Changed the application definition parsing logic to use Comparison* keys (e.g., ComparisonPairWith, ComparisonPrimary) instead of Swipe* keys, and updated the parsing function to parseComparisonPairs. ([[1]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-b1721891163f79a2e27b36e56c5313d7c40c3112f719b0ff4183861fd1d90e9bL10-R18), [[2]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-b1721891163f79a2e27b36e56c5313d7c40c3112f719b0ff4183861fd1d90e9bL28-R39), [[3]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-b1721891163f79a2e27b36e56c5313d7c40c3112f719b0ff4183861fd1d90e9bR49-R50), [[4]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-b149e4949d36eb0bea0deef3b28dfcfee7617191327fb008f2a546f95c433315L15-R15), [[5]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-b149e4949d36eb0bea0deef3b28dfcfee7617191327fb008f2a546f95c433315R147), [[6]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-b149e4949d36eb0bea0deef3b28dfcfee7617191327fb008f2a546f95c433315L168-R170))

Documentation and context:

  • Updated CONTEXT.md to define the new comparison terminology and relationships, clarifying the distinction between comparison pairs and rendering modes. Added an ADR documenting the motivation for this generalization. ([[1]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-86fee53c33308768d49a4ac0ff5720fd7f5411a8c8bd62ab9b80bbffa2977a14L3-R3), [[2]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-86fee53c33308768d49a4ac0ff5720fd7f5411a8c8bd62ab9b80bbffa2977a14R23-R71), [[3]](https://github.com/jumpinjackie/mapguide-react-layout/pull/1651/files#diff-0f1b984675062aec948730970267fbd15184ce5a634ecd07213085c35da184dbR1-R3))

These changes lay the groundwork for supporting multiple map comparison renderers in the viewer and clarify the internal and external APIs around map comparison.

Fixes #1650

jumpinjackie and others added 10 commits May 21, 2026 14:25
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two bugs prevented popover toolbar buttons from showing in the generic
viewer layout template:

1. Z-index timing gap: react-tiny-popover applies containerStyle via
   useEffect (after browser paint), so the portal container was briefly
   at z-index:auto before the first paint. For positioned elements in the
   same stacking context, z-index:auto stacks below elements with explicit
   positive z-index (e.g. the toolbar group at z-index:10). Switched from
   containerStyle to containerClassName + a CSS rule, which applies z-index
   synchronously at element-creation time via useState initialiser in
   react-tiny-popover's useElementRef.

2. focus() stealing in onMouseDown: spy mode added tabIndex+focus() to
   map-viewer-component so keyboard ArrowUp/Down resize the spy cursor.
   That focus() was called for every mousedown inside the component,
   including clicks on toolbar popover-trigger buttons, stealing focus
   before the click event completed and potentially disrupting the
   click-outside detection cycle. Fixed by only calling focus() when the
   mousedown target is not a button/input/interactive element.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r height

react-tiny-popover uses document.body as the default boundary element when
positioning popovers. In generic.html, html and body had no explicit height,
so getBoundingClientRect() returned height=0 / bottom=0.

This caused react-tiny-popover's nudging algorithm to force the popover top
to (0 - popoverHeight = -186px), placing it entirely above the viewport.

Adding height: 100% to html, body, and #map ensures the body bounding rect
covers the full viewport, so popover boundary calculations are correct.

Diagnosed via Playwright: scoutRect.top was 0, but the boundary bottom was
also 0, causing finalTop = popoverHeight * -1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ViewerOptions placeholder renders its own content without a background.
Unlike the map switcher popover which already wraps content in a Card, the
viewer options popover was passing the placeholder directly, resulting in a
transparent background that showed the map through the popover content.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…p, ESC to close

Swipe mode:
- Primary/secondary labels now center-aligned within their respective halves
  of the map (instead of left/right edges), positioned relative to swipe divider

Spy mode:
- Removed static primary/secondary labels and Close Comparison button
- Added a tracking tooltip that follows the spy cursor, positioned outside
  the spy circle (to its right), showing primary/secondary map names and
  an ESC-to-close hint
- ESC key now closes comparison via a document-level keydown listener,
  so it works without requiring map focus; ArrowUp/Down resize still
  require map focus
- Added MAP_SPY_ESC_HINT string ("Press ESC to close")

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… in one flex row

Labels now live in a single absolutely-positioned flex row centered on
the divider line, so Primary and Secondary sit at a fixed gap directly
beside the Close Comparison button regardless of where the splitter is.

Removed the ComparisonLabels helper component (no longer needed) and
extracted a SWIPE_LABEL_STYLE constant for the shared badge styling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace ⬤/◯ icon prefixes with "Primary:" / "Secondary:" text labels,
showing the map's display name (primaryLabel/secondaryLabel if set,
falling back to primaryMapName/secondaryMapName).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Bundle Size Report ✅ PASS

Metric Size Gzip Brotli
Total 10.23 MB 2.29 MB 1.63 MB
Diff +22.01 KB 🔴 ↑ +3.87 KB 🔴 ↑ +2.75 KB 🔴 ↑
Change +0.21% +0.17% +0.16%

Top Changes

File Before After Diff
~ viewer.js 123.16 KB 124.62 KB +1.46 KB 🔴 ↑
~ viewer-debug.js 201.30 KB 202.55 KB +1.25 KB 🔴 ↑
~ viewer-debug.css 9.15 KB 9.19 KB +33.00 B 🔴 ↑
~ viewer.css 8.79 KB 8.81 KB +14.00 B 🔴 ↑

Generated by build-size-diff Commit: 27b65bb

@coveralls
Copy link
Copy Markdown

Coverage Status

coverage: 60.54% (-0.3%) from 60.795% — feature/spy-mode into master

@jumpinjackie jumpinjackie merged commit 2a0d087 into master May 21, 2026
4 of 5 checks passed
@jumpinjackie jumpinjackie deleted the feature/spy-mode branch May 21, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support layer spy mode

2 participants