Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for client or PM side to play/pause ads #101

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
50 changes: 47 additions & 3 deletions src/preview/dist/js/index_bundle.js

Large diffs are not rendered by default.

171 changes: 165 additions & 6 deletions src/preview/src/components/AdPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,30 @@ import DoneIcon from "@mui/icons-material/Done";
import ClearIcon from "@mui/icons-material/Clear";
import Quality from "@mui/icons-material/CameraEnhance";
import InfoIcon from '@mui/icons-material/Info';
import PlayCircleIcon from '@mui/icons-material/PlayCircle';
import PauseCircleIcon from '@mui/icons-material/PauseCircle';
import SkipNextIcon from '@mui/icons-material/SkipNext';
import Forward5Icon from '@mui/icons-material/Forward5';

import LoadingButton from "@mui/lab/LoadingButton";

gsap.registerPlugin(GSDevTools);

export const AdPreview = (props) => {
const {ad, gsdevtools, timestamp, maxFileSize = 150} = props;

const [paused, setPaused] = useState(false);
const [animationForPauseCurrentTime, setAnimationForPauseCurrentTime] = useState(0);
const [animationForPauseDuration, setAnimationForPauseDuration] = useState(0);
const [animationForPause, setAnimationForPause] = useState(null);

const cachedHTML = `${ad.output.html.url}?r=${timestamp}`

const [mediaType, setMediaType] = useState("iframe");
const [mediaSource, setMediaSource] = useState(cachedHTML);
const [activeConfigTab, setActiveConfigTab] = useState("html,iframe");

const [animation, setAnimation] = useState();
const [animation, setAnimation] = useState(null);

const adPreviewCard = useRef();

Expand All @@ -48,8 +57,10 @@ export const AdPreview = (props) => {
// have to use onload in order to set events to the right element (React render thing)
ifr.onload = () => {
if (!ifr.contentWindow) return
ifr.contentWindow.addEventListener("getMainTimeline", e => setAnimation(e.detail), false)
const setAnim = e => setAnimation(e.detail)
ifr.contentWindow.addEventListener("getMainTimeline", setAnim)
ifr.contentWindow.dispatchEvent(new CustomEvent("previewReady"))
return () => ifr.contentWindow.removeEventListener("getMainTimeline", setAnim)
}
}, [mediaSource])

Expand Down Expand Up @@ -89,6 +100,114 @@ export const AdPreview = (props) => {
}
}, [activeConfigTab]);

function reload() {
activeConfigTab === "html,iframe" ? (adPreviewCard.current.src = cachedHTML) : setActiveConfigTab("html,iframe");
setPaused(false)
setAnimationForPauseCurrentTime(0)
setAnimationForPause(undefined)
setAnimation(undefined)
}

// reload all
useEffect(() => {
const reloadAll = event => {
if (event.key == 'r') {
reload()
}
}

window.addEventListener('keydown', reloadAll)
return () => window.removeEventListener("keydown", reloadAll);
}, [])

// skip all
useEffect(() => {
const arrowRightClick = event => {
if (event.key == 'ArrowRight') {
if (!animationForPause) return
animationForPause.progress(1)
}
}
window.addEventListener('keydown', arrowRightClick)
return () => window.removeEventListener('keydown', arrowRightClick)
}, [animationForPause])

// seek 250ms
function seek() {
if (!animationForPause) return
setPaused(true)
animationForPause.seek(animationForPause.time() + 0.25, false)
}

useEffect(() => {
const dotClick = event => {
if (event.key == '.') {
if (!animationForPause) return
seek()
}
}
window.addEventListener('keydown', dotClick)
return () => window.removeEventListener('keydown', dotClick)
}, [animationForPause])

// read spacebar pause press
useEffect(() => {
const pauseAll = event => {
if (event.key == ' ' && gsdevtools !== 'true') {
event.preventDefault()
setPaused(x => !x)
}
}

window.addEventListener('keydown', pauseAll)
return () => window.removeEventListener("keydown", pauseAll);
}, [])

// add play/pause listeners
useEffect(() => {
if (gsdevtools == "true") return; // skip if gsdevtools present

const ifr = adPreviewCard.current

// have to use onload in order to set events to the right element (React render thing)
ifr.onload = () => {
if (!ifr.contentWindow) return
const setAnim = e => setAnimationForPause(e.detail)
ifr.contentWindow.addEventListener("getMainTimeline", setAnim)
ifr.contentWindow.dispatchEvent(new CustomEvent("previewReady"))
return () => ifr.contentWindow.removeEventListener("getMainTimeline", setAnim)
}
}, [mediaSource])

useEffect(() => {
if (!animationForPause) return
setAnimationForPauseDuration(animationForPause.duration())
setAnimationForPauseCurrentTime(animationForPause.time())
const onComplete = animationForPause.eventCallback('onComplete')
animationForPause.eventCallback('onComplete', () => {
onComplete && onComplete()
setPaused(true)
})
const onUpdate = animationForPause.eventCallback('onUpdate')
animationForPause.eventCallback('onUpdate', () => {
onUpdate && onUpdate()
setAnimationForPauseCurrentTime(animationForPause.time())
})
}, [animationForPause])

useEffect(() => {
if (!animationForPause) return

if (paused) animationForPause.pause()
else {
if (animationForPause.progress() == 1) {
reload()
} else {
animationForPause.play()
}
}
}, [paused, animationForPause])

return (
<Card sx={{minWidth: `${ad.width}px`, maxWidth: `${ad.width}px`, height: "fit-content"}} className="card">
<Typography sx={{padding: "0px 10px", margin: "10px 0", wordBreak: "break-all"}} align="center" variant="body2">
Expand Down Expand Up @@ -120,13 +239,55 @@ export const AdPreview = (props) => {
<CardMedia ref={adPreviewCard} component={mediaType} scrolling="no" style={{width: ad.width, height: ad.height}} height={ad.height} width={ad.width} src={mediaSource} id={ad.bundleName} className={ad.bundleName} frameBorder="0" autoPlay controls />
<CardContent>
{
gsdevtools === "true" && animation && activeConfigTab.split(",")[0] === "html"
gsdevtools === "true" && animation !== null && activeConfigTab.split(",")[0] === "html"
? <>
<Box ref={gsDevContainer}></Box>
<Divider light sx={{margin: "20px 0"}} />
</>
: <></>
}
{
gsdevtools !== 'true' && animationForPause !== null && activeConfigTab === "html,iframe" && !ad.controlsOff
? <>
<Box marginBottom="20px" display="flex" flexWrap="wrap" gap="10px" justifyContent="space-between" alignItems="center" className="controls">
<Box>
<Tooltip title="▶/❚❚">
<IconButton
onClick={() => {
setPaused(!paused)
}}
color="primary"
>
{paused ? <PlayCircleIcon /> : <PauseCircleIcon />}
</IconButton>
</Tooltip>
<Tooltip title="Seek to the end">
<IconButton
onClick={() => {
animationForPause?.progress(1)
}}
color="primary"
>
<SkipNextIcon />
</IconButton>
</Tooltip>
<Tooltip title="Seek 250ms">
<IconButton
onClick={() => seek()}
color="primary"
>
<Forward5Icon />
</IconButton>
</Tooltip>
</Box>
<Typography>
{animationForPauseCurrentTime.toFixed(2)} / {animationForPauseDuration.toFixed(2)}s
</Typography>
</Box>
<Divider light sx={{margin: "20px 0"}} />
</>
: <></>
}
{
ad.output?.zip?.size || ad.output?.unzip?.size || ad.quality
? <>
Expand Down Expand Up @@ -177,9 +338,7 @@ export const AdPreview = (props) => {
<Box>
<Tooltip title="Reload">
<IconButton
onClick={(e) => {
activeConfigTab === "html,iframe" ? (adPreviewCard.current.src = cachedHTML) : setActiveConfigTab("html,iframe");
}}
onClick={() => { reload() }}
color="primary"
>
<ReplayIcon />
Expand Down
41 changes: 30 additions & 11 deletions src/preview/src/components/Previews.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ export default function Previews({ data }) {

const [gsdevtools, setGSDevTools] = useState(searchParams.get('gsdevtools'));

const [didMount, setDidMount] = useState(false);
useEffect(() => { setDidMount(true) }, []);

const [ads, setAds] = useState([]);
const [filters, setFilters] = useState(getFiltersFromAds(data.ads, searchParams));

Expand All @@ -104,12 +107,14 @@ export default function Previews({ data }) {

useEffect(() => {
if (gsdevtools !== "true") return
window.addEventListener('keydown', (e) => {
const preventSpace = e => {
if (e.defaultPrevented) return;
if (e.key === " ") {
e.preventDefault();
}
})
}
window.addEventListener('keydown', preventSpace)
return () => window.removeEventListener('keydown', preventSpace)
}, [])

const getSelectedFilters = () => {
Expand Down Expand Up @@ -175,17 +180,31 @@ export default function Previews({ data }) {
}, [page, itemsPerPage, ads]);

// toggle devtools
let GSKeySequence = []
document.addEventListener('keydown', event => {
GSKeySequence.push(event.key)
if (GSKeySequence.includes('g') && GSKeySequence.includes('s')) {
setGSDevTools(!gsdevtools)
useEffect(() => {
let GSKeySequence = []
const keyDown = event => {
GSKeySequence.push(event.key)
if (GSKeySequence.includes('g') && GSKeySequence.includes('s')) {
setGSDevTools(x => !x)
}
}
const keyUp = event => {
GSKeySequence = GSKeySequence.filter(key => key !== event.key)
}
window.addEventListener('keydown', keyDown)
window.addEventListener('keyup', keyUp)

return () => {
window.removeEventListener('keydown', keyDown)
window.removeEventListener('keyup', keyUp)
}
}, [])

useEffect(() => {
if (didMount) {
window.location.reload()
}
})
document.addEventListener('keyup', event => {
GSKeySequence = GSKeySequence.filter(key => key !== event.key)
})
}, [gsdevtools])

// generate page
return (
Expand Down
6 changes: 6 additions & 0 deletions src/preview/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ code {
}
}
}

@container (max-width: 200px) {
.controls {
justify-content: center !important;
}
}
3 changes: 3 additions & 0 deletions src/webpack/buildPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ module.exports = async function buildPreview(result, qualities, outputDir) {
},
info: (result && result[bundleName])
? result[bundleName].settings.data.settings.info
: undefined,
controlsOff: (result && result[bundleName])
? result[bundleName].settings.data.settings.controlsOff
: undefined
}
})
Expand Down
3 changes: 2 additions & 1 deletion src/webpack/devServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ ${chalk.grey.bold('-------------------------------------------------------')}
url,
},
},
info: e.data.settings.info
info: e.data.settings.info,
controlsOff: e.data.settings.controlsOff,
}
})
})
Expand Down
3 changes: 2 additions & 1 deletion src/webpack/devServerParallel.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ ${chalk.grey.bold('-------------------------------------------------------')}
url,
},
},
info: e.data.settings.info
info: e.data.settings.info,
controlsOff: e.data.settings.controlsOff,
}
})
})
Expand Down