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. +

+ + ), + }, +}; + +export const SINGLE_SELLER_CONFIG = { + SSP_TAG: { + title: 'SSP Tag', + info: ( + <> +

+ 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: +

+ + + ), + }, + SSP: { + title: 'SSP', + info: ( + <> +

+ 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. +

+ + ), + }, + LOAD_INTEREST_GROUP: { + title: 'Load Interest Group', + info: ( + <> +

+ 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. +

+ + ), + }, + KEY_VALUE_DSP_SERVER: { + title: 'Key/Value', + description: 'Trusted DSP Server', + info: ( + <> +

+ 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([]); + const [info, setInfo] = useState(null); const [interactiveMode, _setInteractiveMode] = useState(false); @@ -225,14 +226,14 @@ const ExplorableExplanation = () => { { title: 'Info', content: { - Element: InfoPanel, + Element: Info, props: { - data: undefined, + data: info, }, }, }, ], - [auctionsData, highlightedInterestGroup, interestGroupsData] + [auctionsData, highlightedInterestGroup, interestGroupsData, info] ); return ( @@ -242,6 +243,8 @@ const ExplorableExplanation = () => { setCurrentSite={_setCurrentSiteData} setInteractiveMode={setInteractiveMode} interactiveMode={interactiveMode} + info={info} + setInfo={setInfo} setHighlightedInterestGroup={setHighlightedInterestGroup} isMultiSeller={isMultiSeller} setIsMultiSeller={setIsMultiSeller} diff --git a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/panel.tsx b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/panel.tsx index 582353fe1..c84f92e22 100644 --- a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/panel.tsx +++ b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/panel.tsx @@ -28,7 +28,7 @@ import { app, userSketch, interestGroupSketch, - sketch, + sketch as mainSketch, } from '@google-psat/explorable-explanations'; import { ReactP5Wrapper } from '@p5-wrapper/react'; import { useTabs } from '@google-psat/design-system'; @@ -58,6 +58,8 @@ interface PanelProps { setIsMultiSeller: React.Dispatch>; interactiveMode: boolean; setInteractiveMode: (event: React.ChangeEvent) => void; + setInfo: React.Dispatch>; + info: string | null; } const Panel = ({ @@ -68,6 +70,8 @@ const Panel = ({ setIsMultiSeller, interactiveMode, setInteractiveMode, + info, + setInfo, }: PanelProps) => { const [play, setPlay] = useState(true); const [sliderStep, setSliderStep] = useState(1); @@ -104,6 +108,12 @@ const Panel = ({ setActiveTab: actions.setActiveTab, })); + useEffect(() => { + if (info) { + setActiveTab(3); + } + }, [info, setActiveTab]); + useEffect(() => { document.addEventListener('keydown', tooglePlayOnKeydown); @@ -276,7 +286,7 @@ const Panel = ({ {/* Main Canvas */} - + {/* Interest Group Canvas */} diff --git a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/auctions.tsx b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/tableTabPanels/auctions.tsx similarity index 88% rename from packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/auctions.tsx rename to packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/tableTabPanels/auctions.tsx index aba66075f..138c3057b 100644 --- a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/auctions.tsx +++ b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/tableTabPanels/auctions.tsx @@ -27,10 +27,10 @@ import type { /** * Internal dependencies. */ -import AdUnitsPanel from '../adUnits/panel'; -import type { AuctionEventsType } from '../../../../stateProviders/protectedAudience/context'; -import AuctionsContainer from '../auctions/container'; -import type { AdUnitLiteral } from './auctionEventTransformers'; +import AdUnitsPanel from '../../adUnits/panel'; +import type { AuctionEventsType } from '../../../../../stateProviders/protectedAudience/context'; +import AuctionsContainer from '../../auctions/container'; +import type { AdUnitLiteral } from '../auctionEventTransformers'; interface AuctionsProps { auctionEvents: { diff --git a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/infoPanel.tsx b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/tableTabPanels/info.tsx similarity index 69% rename from packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/infoPanel.tsx rename to packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/tableTabPanels/info.tsx index 9ea460353..c7e552e61 100644 --- a/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/infoPanel.tsx +++ b/packages/extension/src/view/devtools/components/privateAdvertising/protectedAudience/explorableExplanation/tableTabPanels/info.tsx @@ -13,35 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /** * External dependencies. */ -import { prettyPrintJson } from 'pretty-print-json'; import React from 'react'; -interface InfoPanelProps { - data: any; +interface InfoProps { + data: { + title: string; + description?: string; + info: string; + key: number; + }; } -const InfoPanel = ({ data }: InfoPanelProps) => { +const Info = ({ data }: InfoProps) => { + const { title, description, info } = data; + return (
- {data ? ( -
-
-            
-
+ {info ? ( +
+

+ {title + ' ' + (description || '')} +

+
{info}
) : (

- No data available + No information available

)} @@ -49,4 +50,4 @@ const InfoPanel = ({ data }: InfoPanelProps) => { ); }; -export default InfoPanel; +export default Info;