diff --git a/packages/explorable-explanations/src/protectedAudience/app.js b/packages/explorable-explanations/src/protectedAudience/app.js index 1e4805a32..3518e5782 100644 --- a/packages/explorable-explanations/src/protectedAudience/app.js +++ b/packages/explorable-explanations/src/protectedAudience/app.js @@ -22,6 +22,7 @@ const app = { expandIconPositions: [], currentIndex: 0, pausedReason: '', + infoIconsPositions: [], }, color: null, auction: { @@ -66,6 +67,7 @@ const app = { visitedIndexOrderTracker: -1, isRevisitingNodeInInteractiveMode: false, setCurrentSite: () => null, + setInfo: () => null, setHighlightedInterestGroup: () => null, setPlayState: () => null, getPlayState: () => null, @@ -76,6 +78,7 @@ const app = { mouseOver: {}, mouseOut: {}, mouseMoved: {}, + mouseClicked: {}, }, }, }; diff --git a/packages/explorable-explanations/src/protectedAudience/canvas/setupMainCanvas.js b/packages/explorable-explanations/src/protectedAudience/canvas/setupMainCanvas.js index 05b4452b8..cdf75f8aa 100644 --- a/packages/explorable-explanations/src/protectedAudience/canvas/setupMainCanvas.js +++ b/packages/explorable-explanations/src/protectedAudience/canvas/setupMainCanvas.js @@ -25,7 +25,7 @@ import { calculateCanvasDimensions } from '../utils'; */ import throttle from 'just-throttle'; -export const setupMainCanvas = async (p, doNotPlay = false) => { +export const setupMainCanvas = async (p, pause = false) => { try { const { height, width } = calculateCanvasDimensions(); const canvas = p.createCanvas(width, height); @@ -49,6 +49,18 @@ export const setupMainCanvas = async (p, doNotPlay = false) => { } }); + canvas.mouseClicked(() => { + const callbacks = app.canvasEventListerners.main.mouseClicked; + + Object.keys(callbacks).forEach((key) => { + const callback = callbacks[key]; + + if (typeof callback === 'function') { + callback(p.mouseX, p.mouseY); + } + }); + }); + const mouseMovedCallback = throttle(() => { const callbacks = app.canvasEventListerners.main.mouseMoved; @@ -66,7 +78,7 @@ export const setupMainCanvas = async (p, doNotPlay = false) => { app.setUpTimeLine(); if (!app.isInteractiveMode) { - await app.play(false, doNotPlay); + await app.play(false, pause); } } catch (error) { // eslint-disable-next-line no-console -- We should know the error and let it fail silently since it doesnt break anything. diff --git a/packages/explorable-explanations/src/protectedAudience/components/box.js b/packages/explorable-explanations/src/protectedAudience/components/box.js index e475ad352..2e25ad3d0 100644 --- a/packages/explorable-explanations/src/protectedAudience/components/box.js +++ b/packages/explorable-explanations/src/protectedAudience/components/box.js @@ -20,7 +20,6 @@ import app from '../app'; import config from '../config'; import { addCanvasEventListener, isInsideBox } from '../utils'; -const INFO_ICON_SIZE = 16; const INFO_ICON_SPACING = 3; const Box = ({ @@ -43,14 +42,23 @@ const Box = ({ }, }; + const { + flow: { + colors: { + box: { background, text }, + }, + }, + timeline: { infoIconSize }, + } = config; + const p = app.p; p.push(); p.textAlign(p.CENTER, p.CENTER); - p.fill(color || config.flow.colors.box.background); + p.fill(color || background); p.rect(x, y, width, height); p.strokeWeight(0.1); - p.fill(config.flow.colors.box.text); + p.fill(text); p.textFont('sans-serif'); if (description) { @@ -61,22 +69,25 @@ const Box = ({ } if (info) { - const iconX = x + width - INFO_ICON_SIZE - INFO_ICON_SPACING; + const iconX = x + width - infoIconSize - INFO_ICON_SPACING; const iconY = y + INFO_ICON_SPACING; + app.timeline.infoIconsPositions.push({ x: iconX, y: iconY }); - p.image(p.infoIcon, iconX, iconY, INFO_ICON_SIZE, INFO_ICON_SIZE); + p.image(p.infoIcon, iconX, iconY, infoIconSize, infoIconSize); - const callback = (mouseX, mouseY) => { - if (isInsideBox(mouseX, mouseY, iconX, iconY, INFO_ICON_SIZE)) { - app.p.cursor('pointer'); - console.log(info); // eslint-disable-line no-console - } else { - app.p.cursor('default'); + const mouseClickedCallback = (mouseX, mouseY) => { + if (isInsideBox(mouseX, mouseY, iconX, iconY, infoIconSize)) { + app.setInfo({ + title, + description, + info, + key: Date.now(), // To force change the state, so that the info modal is shown in case of same value. + }); } }; const key = title + description + x + y; - addCanvasEventListener('mouseMoved', callback, key); + addCanvasEventListener('mouseClicked', mouseClickedCallback, key); } p.pop(); diff --git a/packages/explorable-explanations/src/protectedAudience/config.js b/packages/explorable-explanations/src/protectedAudience/config.js index d62ba7ea4..32fad5c5f 100644 --- a/packages/explorable-explanations/src/protectedAudience/config.js +++ b/packages/explorable-explanations/src/protectedAudience/config.js @@ -34,6 +34,7 @@ const config = { height: 30, }, expandIconSize: 20, + infoIconSize: 16, circles: [ { type: 'advertiser', diff --git a/packages/explorable-explanations/src/protectedAudience/index.js b/packages/explorable-explanations/src/protectedAudience/index.js index c0ea5659e..cae8d7cc8 100644 --- a/packages/explorable-explanations/src/protectedAudience/index.js +++ b/packages/explorable-explanations/src/protectedAudience/index.js @@ -661,6 +661,10 @@ export const interestGroupSketch = (p) => { app.setCurrentSite = props.setCurrentSite; } + if (props.setInfo) { + app.setInfo = props.setInfo; + } + if (props.setHighlightedInterestGroup) { app.setHighlightedInterestGroup = props.setHighlightedInterestGroup; } @@ -705,6 +709,7 @@ app.reset = async () => { app.timeline.isPaused = true; app.setPlayState(false); app.setCurrentSite(null); + app.setInfo({}); }; app.createCanvas = () => { diff --git a/packages/explorable-explanations/src/protectedAudience/info.json b/packages/explorable-explanations/src/protectedAudience/info.json deleted file mode 100644 index 5d2999f68..000000000 --- a/packages/explorable-explanations/src/protectedAudience/info.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "joinInterestGroups": [ - { - "title": "DSP Tags", - "info": "Content for DSP Tags" - }, - { - "title": "DSPs", - "info": "Content for DSPs" - } - ], - "auction": { - "singleSeller": [ - [ - { - "title": "adunit-code", - "description": "div-200-1", - "info": "Content for First Price Auction" - }, - { - "title": "adunit-code", - "description": "div-200-2", - "info": "Content for First Price Auction" - }, - { - "title": "adunit-code", - "description": "div-200-3", - "info": "Content for First Price Auction" - } - ], - [ - { - "date": "2024-10-02", - "time": "10:00:22PM" - }, - { - "date": "2024-10-3", - "time": "10:00:12PM" - }, - { - "date": "2024-10-04", - "time": "10:10:22PM" - } - ], - { - "title": "SSP Tag", - "info": "Content for First Price Auction" - } - ], - "multiSeller": [] - } -} diff --git a/packages/explorable-explanations/src/protectedAudience/modules/auctions/index.js b/packages/explorable-explanations/src/protectedAudience/modules/auctions/index.js index b40af295c..95225523e 100644 --- a/packages/explorable-explanations/src/protectedAudience/modules/auctions/index.js +++ b/packages/explorable-explanations/src/protectedAudience/modules/auctions/index.js @@ -152,6 +152,7 @@ auction.draw = (index) => { app.promiseQueue.push((cb) => { if (!app.isRevisitingNodeInInteractiveMode) { flow.clearBelowTimelineCircles(); + app.timeline.infoIconsPositions = []; } cb(null, true); }); diff --git a/packages/explorable-explanations/src/protectedAudience/modules/auctions/setUpRunadAuction.js b/packages/explorable-explanations/src/protectedAudience/modules/auctions/setUpRunadAuction.js index 388303709..99c83ce23 100644 --- a/packages/explorable-explanations/src/protectedAudience/modules/auctions/setUpRunadAuction.js +++ b/packages/explorable-explanations/src/protectedAudience/modules/auctions/setUpRunadAuction.js @@ -19,6 +19,7 @@ import app from '../../app'; import config from '../../config'; import { Box, ProgressLine } from '../../components'; +import { SINGLE_SELLER_CONFIG } from '../flowConfig.jsx'; const setUpRunadAuction = (steps, afterRippleStep = null) => { const { box, colors } = config.flow; @@ -26,9 +27,10 @@ const setUpRunadAuction = (steps, afterRippleStep = null) => { steps.push({ component: Box, props: { - title: 'runAdAuction', + title: SINGLE_SELLER_CONFIG.RUN_AD_AUCTION.title, x: () => app.auction.nextTipCoordinates?.x + 10, y: () => app.auction.nextTipCoordinates?.y - box.height / 2, + info: SINGLE_SELLER_CONFIG.RUN_AD_AUCTION.info, }, delay: 1000, callBack: (returnValue) => { @@ -38,23 +40,26 @@ const setUpRunadAuction = (steps, afterRippleStep = null) => { const boxes = [ { - title: 'Load Interest Group', + title: SINGLE_SELLER_CONFIG.LOAD_INTEREST_GROUP.title, extraProps: { showBarrageAnimation: true, }, + info: SINGLE_SELLER_CONFIG.LOAD_INTEREST_GROUP.info, }, { - title: 'Key/Value Trusted', - description: 'DSP Server', + title: SINGLE_SELLER_CONFIG.KEY_VALUE_DSP_SERVER.title, + description: SINGLE_SELLER_CONFIG.KEY_VALUE_DSP_SERVER.description, color: colors.box.notBrowser, + info: SINGLE_SELLER_CONFIG.KEY_VALUE_DSP_SERVER.info, }, { - title: 'generateBid()', - description: '(from DSPs on dsp.js)', + title: SINGLE_SELLER_CONFIG.GENERATE_BID.title, + description: SINGLE_SELLER_CONFIG.GENERATE_BID.description, color: colors.box.notBrowser, extraProps: { showRippleEffect: true, }, + info: SINGLE_SELLER_CONFIG.GENERATE_BID.info, }, { title: 'DSP 1', @@ -63,25 +68,29 @@ const setUpRunadAuction = (steps, afterRippleStep = null) => { title: 'DSP 2', }, { - title: 'Key/Value Trusted', - description: 'SSP Server', + title: SINGLE_SELLER_CONFIG.KEY_VALUE_SSP_SERVER.title, + description: SINGLE_SELLER_CONFIG.KEY_VALUE_SSP_SERVER.description, + info: SINGLE_SELLER_CONFIG.KEY_VALUE_SSP_SERVER.info, }, { - title: 'scoreAd()', - description: '(by SSPs on ssp.js)', + title: SINGLE_SELLER_CONFIG.SCORE_AD.title, + description: SINGLE_SELLER_CONFIG.SCORE_AD.description, + info: SINGLE_SELLER_CONFIG.SCORE_AD.info, }, { - title: 'reportWin()', - description: '(on dsp.js)', + title: SINGLE_SELLER_CONFIG.REPORT_WIN.title, + description: SINGLE_SELLER_CONFIG.REPORT_WIN.description, color: colors.box.notBrowser, + info: SINGLE_SELLER_CONFIG.REPORT_WIN.info, }, { - title: 'reportResult()', - description: '(on ssp.js)', + title: SINGLE_SELLER_CONFIG.REPORT_RESULT.title, + description: SINGLE_SELLER_CONFIG.REPORT_RESULT.description, + info: SINGLE_SELLER_CONFIG.REPORT_RESULT.info, }, ]; - boxes.forEach(({ title, description, color, extraProps = {} }) => { + boxes.forEach(({ title, description, color, info, extraProps = {} }) => { if (title === 'DSP 1') { if (afterRippleStep) { steps.push(afterRippleStep()); @@ -108,6 +117,7 @@ const setUpRunadAuction = (steps, afterRippleStep = null) => { x: () => app.auction.nextTipCoordinates?.x + 10, y: () => app.auction.nextTipCoordinates?.y - box.height + 15, color, + info, }, delay: 1000, callBack: (returnValue) => { @@ -154,6 +164,7 @@ const setUpRunadAuction = (steps, afterRippleStep = null) => { x: () => app.auction.nextTipCoordinates?.x + 10, y: () => app.auction.nextTipCoordinates?.y, color, + info, }, delay: 1000, callBack: (returnValue) => { @@ -204,6 +215,7 @@ const setUpRunadAuction = (steps, afterRippleStep = null) => { x: () => app.auction.nextTipCoordinates?.x - box.width / 2, y: () => app.auction.nextTipCoordinates?.y + 10, color, + info, }, delay: 1000, callBack: (returnValue) => { diff --git a/packages/explorable-explanations/src/protectedAudience/modules/auctions/single-seller/setupFirstSSPTagFlow.js b/packages/explorable-explanations/src/protectedAudience/modules/auctions/single-seller/setupFirstSSPTagFlow.js index c69162364..7fe156d39 100644 --- a/packages/explorable-explanations/src/protectedAudience/modules/auctions/single-seller/setupFirstSSPTagFlow.js +++ b/packages/explorable-explanations/src/protectedAudience/modules/auctions/single-seller/setupFirstSSPTagFlow.js @@ -19,6 +19,7 @@ import app from '../../../app'; import config from '../../../config'; import { Box, ProgressLine } from '../../../components'; +import { SINGLE_SELLER_CONFIG } from '../../flowConfig.jsx'; const setUpSingleSellerFirstSSPTagFlow = (steps) => { const { box, colors } = config.flow; @@ -39,9 +40,10 @@ const setUpSingleSellerFirstSSPTagFlow = (steps) => { steps.push({ component: Box, props: { - title: 'SSP Tag', + title: SINGLE_SELLER_CONFIG.SSP_TAG.title, x: () => app.auction.nextTipCoordinates?.x - box.width / 2, y: () => app.auction.nextTipCoordinates?.y, + info: SINGLE_SELLER_CONFIG.SSP_TAG.info, }, delay: 1000, callBack: (returnValue) => { @@ -64,10 +66,11 @@ const setUpSingleSellerFirstSSPTagFlow = (steps) => { steps.push({ component: Box, props: { - title: 'SSP', + title: SINGLE_SELLER_CONFIG.SSP.title, x: () => app.auction.nextTipCoordinates?.x - box.width / 2, y: () => app.auction.nextTipCoordinates?.y + config.flow.arrowSize, color: colors.box.noData, + info: SINGLE_SELLER_CONFIG.SSP.info, }, delay: 1000, callBack: (returnValue) => { @@ -90,10 +93,11 @@ const setUpSingleSellerFirstSSPTagFlow = (steps) => { steps.push({ component: Box, props: { - title: 'DSPs', + title: SINGLE_SELLER_CONFIG.DSPS.title, x: () => app.auction.nextTipCoordinates?.x - box.width / 2, y: () => app.auction.nextTipCoordinates?.y + config.flow.arrowSize, color: colors.box.noData, + info: SINGLE_SELLER_CONFIG.DSPS.info, }, delay: 1000, callBack: (returnValue) => { diff --git a/packages/explorable-explanations/src/protectedAudience/modules/flowConfig.jsx b/packages/explorable-explanations/src/protectedAudience/modules/flowConfig.jsx new file mode 100644 index 000000000..5c82f24b2 --- /dev/null +++ b/packages/explorable-explanations/src/protectedAudience/modules/flowConfig.jsx @@ -0,0 +1,258 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * https://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 React from 'react'; + +export const ADVERTIZER_CONFIG = { + DSP_TAGS: { + title: 'DSP Tags', + info: ( + <> +
+ These are script tags embedded in the advertiser's webpage that enable + communication with DSP servers. They collect user activity data, which + is sent to DSP servers for processing (see the next DSPs box). DSP + tags help associate users with interest groups for retargeting and + audience building in future ad campaigns. +
+ > + ), + }, + DSPS: { + title: 'DSPs', + info: ( + <> +
+ DSP servers on the advertiser's side handle user tracking, data
+ processing, and interest group management. They process user behavior
+ and decide if the user should be added to specific interest groups
+ using the{' '}
+ joinAdInterestGroup()
API.
+ These interest groups are added to the user's browser which are shown
+ with small bubbles here. They store user profiles based on
+ demographics, location, and behavior while ensuring compliance with
+ privacy standards like the Protected Audience API.
+
+ An SSP tag is a script placed on the publisher's page that enables the + SSP to initiate ad requests and pass them to DSPs. It does the + following: +
++ An SSP is a platform that helps publishers manage, sell, and optimize + their ad inventory programmatically. It connects publishers with + multiple demand sources like DSPs, advertisers, and ad exchanges to + facilitate real-time bidding (RTB) auctions. +
+Example: Google Ad Manager, Magnite.
+ > + ), + }, + DSPS: { + title: 'DSPs', + info: ( + <> ++ On the publisher side, DSP servers process ad requests, evaluate bids, + and serve ads based on campaign targeting and bidding strategies. They + handle bid generation, ad selection, and reporting during ad auctions + initiated by SSPs. The DSP server evaluates bid requests in real time, + using signals such as interest groups, contextual relevance, and + advertiser budgets to decide whether to bid. +
+ > + ), + }, + RUN_AD_AUCTION: { + title: 'runAdAuction()', + info: ( + <> +
+ SSP ad tag initiates on-device auction by calling{' '}
+ runAdAuction()
, passing in
+ signals from DSP's openRTB bid response.
+
+ A browser-based mechanism for retrieving user interest group data,
+ stored using the{' '}
+ joinAdInterestGroup()
API,
+ for use in ad auctions. This ensures ads match user preferences while
+ preserving privacy.
+
+ A secure DSP-side server that handles bid generation using key/value + pairs for interest group and contextual data. It ensures data + processing aligns with privacy requirements, creating competitive bids + based on predefined values. +
+ > + ), + }, + GENERATE_BID: { + title: 'generateBid()', + description: '(from DSPs on dsp.js)', + info: ( + <> ++ A DSP-side function that generates a bid for an auction based on + interest group data, contextual signals, and advertiser preferences. + Outputs include bid amount, ad creative, and metadata, ensuring + relevance and competitiveness in the auction. +
+ > + ), + }, + KEY_VALUE_SSP_SERVER: { + title: 'Key/Value', + description: 'Trusted SSP Server', + info: ( + <> ++ A secure SSP-side server responsible for ad scoring and auction + facilitation. It uses key/value pairs to evaluate bids and ensures + data privacy and compliance during ad auctions. +
+ > + ), + }, + SCORE_AD: { + title: 'scroreAd()', + description: '(from SSPs on ssp.js)', + info: ( + <> ++ An SSP-side function that evaluates and ranks bids during an ad + auction based on price, relevance, and publisher-defined criteria. + Outputs a ranked list of bids, selecting the top bid as the winner for + ad display. +
+ > + ), + }, + REPORT_WIN: { + title: 'reportWin()', + description: '(on dsp.js)', + info: ( + <> ++ A function executed by the winning DSP to log auction details, + including bid price and ad performance, for reporting and + optimization. It ensures transparency for campaign performance and + provides data for analytics and billing. +
+ > + ), + }, + REPORT_RESULT: { + title: 'reportResult()', + description: '(on ssp.js)', + info: ( + <> ++ A function executed by the SSP or publisher to log auction results, + providing transparency and analytics to all parties. It tracks metrics + such as winning bids, auction details, and ad performance. +
+ > + ), + }, +}; + +export const MULTI_SELLER_CONFIG = { + COMPONENT_AUCTION: { + title: '', + info: ( + <> ++ A component auction is conducted by a single seller among a collection + of buyers as part of a multi-seller auction process. The winner of + each component auction advances to the top-level auction, which + determines the final ad to be displayed. In unified flows, the + top-level auction includes winners from both contextual and Protected + Audience auctions. +
++ Each component auction adheres to the single-seller unified flow + outlined in the Bidding and Auction Services Design. +
+ + Read more + + > + ), + }, + MULTI_SELLER_AUCTION: { + title: '', + info: ( + <> ++ A multi-seller auction involves multiple sellers offering their + inventory simultaneously, enabling multiple buyers (advertisers or + DSPs) to bid on available items. The auction typically takes place on + a shared platform (e.g., an SSP or ad exchange), where all sellers' + inventories are pooled for bidding. +
++ Each seller conducts a component auction, selecting a winner to + represent them in the top-level auction. The top-level auction + determines the overall winner, whose ad gets displayed. This structure + optimizes revenue for sellers while ensuring fair competition among + buyers. +
+ > + ), + }, +}; diff --git a/packages/explorable-explanations/src/protectedAudience/modules/joinInterestGroup.js b/packages/explorable-explanations/src/protectedAudience/modules/joinInterestGroup.js index 62d803276..c591c9c63 100644 --- a/packages/explorable-explanations/src/protectedAudience/modules/joinInterestGroup.js +++ b/packages/explorable-explanations/src/protectedAudience/modules/joinInterestGroup.js @@ -22,7 +22,7 @@ import config from '../config'; import * as utils from '../utils'; import { Box, ProgressLine } from '../components'; import bubbles from './bubbles'; -import info from '../info.json'; +import { ADVERTIZER_CONFIG } from './flowConfig.jsx'; /** * @module joinInterestGroup @@ -78,10 +78,10 @@ joinInterestGroup.setUp = (index) => { steps.push({ component: Box, props: { - title: info.joinInterestGroups[0].title, + title: ADVERTIZER_CONFIG.DSP_TAGS.title, x: () => app.joinInterestGroup.nextTipCoordinates?.x - box.width / 2, y: () => app.joinInterestGroup.nextTipCoordinates?.y + ARROW_SIZE, - info: info.joinInterestGroups[0].info, + info: ADVERTIZER_CONFIG.DSP_TAGS.info, }, delay: 1000, callBack: (returnValue) => { @@ -106,12 +106,12 @@ joinInterestGroup.setUp = (index) => { steps.push({ component: Box, props: { - title: info.joinInterestGroups[1].title, + title: ADVERTIZER_CONFIG.DSPS.title, x: () => app.joinInterestGroup.nextTipCoordinates?.x - box.width / 2, y: () => app.joinInterestGroup.nextTipCoordinates?.y + config.flow.arrowSize, color: config.flow.colors.box.notBrowser, - info: info.joinInterestGroups[1].info, + info: ADVERTIZER_CONFIG.DSPS.info, }, delay: 1000, callBack: (returnValue) => { @@ -206,6 +206,7 @@ joinInterestGroup.draw = (index) => { app.promiseQueue.push((cb) => { if (!app.isRevisitingNodeInInteractiveMode) { flow.clearBelowTimelineCircles(); + app.timeline.infoIconsPositions = []; } cb(null, true); }); diff --git a/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInInteractiveMode.js b/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInInteractiveMode.js index 3c3fdd4e2..d8748264b 100644 --- a/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInInteractiveMode.js +++ b/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInInteractiveMode.js @@ -18,6 +18,7 @@ */ import app from '../../app'; import config from '../../config'; +import { isInsideBox } from '../isInsideBox'; import { isInsideCircle } from '../isInsideCircle'; import { isOverControls } from '../isOverControls'; import { wipeAndRecreateUserCanvas } from '../wipeAndRecreateCanvas'; @@ -32,12 +33,26 @@ const mouseMovedInInteractiveMode = (event, renderUserIcon) => { let hoveringOnExpandIconPositions = false; let hoveringOnCircles = false; + let hoveredOverIcons = false; const { circlePositions, expandIconPositions } = app.timeline; const { circleProps: { diameter }, + infoIconSize, } = config.timeline; + app.timeline.infoIconsPositions.forEach(({ x: _x, y: _y }) => { + if (isInsideBox(app.p.mouseX, app.p.mouseY, _x, _y, infoIconSize)) { + hoveredOverIcons = true; + } + }); + + if (hoveredOverIcons) { + app.p.cursor('pointer'); + } else { + app.p.cursor('default'); + } + expandIconPositions.forEach((positions) => { if ( isInsideCircle( diff --git a/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInNonInteractiveMode.js b/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInNonInteractiveMode.js index 8e8352920..612edabd4 100644 --- a/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInNonInteractiveMode.js +++ b/packages/explorable-explanations/src/protectedAudience/utils/mouseHandlers/mouseMovedInNonInteractiveMode.js @@ -18,35 +18,42 @@ */ import app from '../../app'; import config from '../../config'; +import { isInsideBox } from '../isInsideBox'; import { isInsideCircle } from '../isInsideCircle'; +const INFO_ICON_SIZE = 16; + const mouseMovedInNonInteractiveMode = (event) => { const { offsetX, offsetY } = event; app.mouseX = offsetX; app.mouseY = offsetY; - let hoveringOnExpandIconPositions = false; - - if (!config.timeline.circles.every(({ visited }) => visited === true)) { - return; - } + let hoveredOverIcons = false; - app.timeline.expandIconPositions.forEach((positions) => { - if ( - isInsideCircle( - offsetX, - offsetY, - positions.x, - positions.y + config.timeline.circleProps.diameter / 2, - 20 - ) - ) { - hoveringOnExpandIconPositions = true; + app.timeline.infoIconsPositions.forEach(({ x: _x, y: _y }) => { + if (isInsideBox(app.p.mouseX, app.p.mouseY, _x, _y, INFO_ICON_SIZE)) { + hoveredOverIcons = true; } }); - if (hoveringOnExpandIconPositions) { + if (config.timeline.circles.every(({ visited }) => visited === true)) { + app.timeline.expandIconPositions.forEach((positions) => { + if ( + isInsideCircle( + offsetX, + offsetY, + positions.x, + positions.y + config.timeline.circleProps.diameter / 2, + 20 + ) + ) { + hoveredOverIcons = true; + } + }); + } + + if (hoveredOverIcons) { app.p.cursor('pointer'); } else { app.p.cursor('default'); diff --git a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/index.tsx b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/index.tsx index 63712ce61..53ca6f308 100644 --- a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/index.tsx +++ b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/index.tsx @@ -32,14 +32,14 @@ import { app, config } from '@google-psat/explorable-explanations'; */ import Panel from './panel'; import IGTable from '../interestGroups/table'; -import Auctions from './auctions'; +import Auctions from '../auctions'; import { SYNTHETIC_INTEREST_GROUPS } from './constants'; +import Info from './tableTabPanels/info'; import { configuredAuctionEvents, type CurrentSiteData, type AdUnitLiteral, } from './auctionEventTransformers'; -import InfoPanel from './infoPanel'; import BidsPanel from '../bids/panel'; const ExplorableExplanation = () => { @@ -52,6 +52,7 @@ const ExplorableExplanation = () => { InterestGroups[] >([]); const [sitesVisited, setSitesVisited] = useState- -+ {info ? ( +
- No data available + No information available