mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:10:46 +00:00
add playwire ads (#1128)
## Description: Add ads on the left and right gutters on homepage, one small in game add above the control panel. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: evan <img width="1710" alt="Screenshot 2025-06-17 at 10 06 02 AM" src="https://github.com/user-attachments/assets/1f8d0114-be2b-4fb4-90e2-86d1c5119b04" /> <img width="368" alt="Screenshot 2025-06-17 at 10 06 32 AM" src="https://github.com/user-attachments/assets/8b56f92a-a9c9-47e1-8d85-23d8c4dbf773" /> ![Uploading Screenshot 2025-06-17 at 10.07.38 AM.png…]()
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
@customElement("gutter-ad-modal")
|
||||
export class GutterAdModal extends LitElement {
|
||||
@state()
|
||||
private isVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private adLoaded: boolean = false;
|
||||
|
||||
private leftAdType: string = "left_rail";
|
||||
private rightAdType: string = "right_rail";
|
||||
private leftContainerId: string = "gutter-ad-container-left";
|
||||
private rightContainerId: string = "gutter-ad-container-right";
|
||||
private margin: string = "10px";
|
||||
|
||||
// Override createRenderRoot to disable shadow DOM
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
static styles = css``;
|
||||
|
||||
// Called after the component's DOM is first rendered
|
||||
firstUpdated() {
|
||||
// DOM is guaranteed to be available here
|
||||
console.log("GutterAdModal DOM is ready");
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
|
||||
// Wait for the update to complete, then load ads
|
||||
this.updateComplete.then(() => {
|
||||
this.loadAds();
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.isVisible = false;
|
||||
this.destroyAds();
|
||||
this.adLoaded = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private loadAds(): void {
|
||||
// Ensure the container elements exist before loading ads
|
||||
const leftContainer = this.querySelector(`#${this.leftContainerId}`);
|
||||
const rightContainer = this.querySelector(`#${this.rightContainerId}`);
|
||||
|
||||
if (!leftContainer || !rightContainer) {
|
||||
console.warn("Ad containers not found in DOM");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.adLoaded) {
|
||||
console.log("Ads already loaded, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.ramp.que.push(() => {
|
||||
window.ramp.spaAddAds([
|
||||
{
|
||||
type: this.leftAdType,
|
||||
selectorId: this.leftContainerId,
|
||||
},
|
||||
{
|
||||
type: this.rightAdType,
|
||||
selectorId: this.rightContainerId,
|
||||
},
|
||||
]);
|
||||
this.adLoaded = true;
|
||||
console.log("Playwire ads loaded:", this.leftAdType, this.rightAdType);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load Playwire ads:", error);
|
||||
}
|
||||
}
|
||||
|
||||
private destroyAds(): void {
|
||||
if (!window.ramp || !this.adLoaded) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.ramp.destroyUnits("all");
|
||||
} catch (error) {
|
||||
console.error("Failed to destroy Playwire ad:", error);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.destroyAds();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<!-- Left Gutter Ad -->
|
||||
<div
|
||||
class="hidden xl:flex fixed left-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] bg-gray-900 border border-gray-600 z-[9999] pointer-events-auto items-center justify-center shadow-lg"
|
||||
style="margin-left: ${this.margin};"
|
||||
>
|
||||
<div
|
||||
id="${this.leftContainerId}"
|
||||
class="w-full h-full flex items-center justify-center p-2"
|
||||
>
|
||||
${!this.adLoaded
|
||||
? html`<span class="text-white text-xs text-center"
|
||||
>Loading ad...</span
|
||||
>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Gutter Ad -->
|
||||
<div
|
||||
class="hidden xl:flex fixed right-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] bg-gray-900 border border-gray-600 z-[9999] pointer-events-auto items-center justify-center shadow-lg"
|
||||
style="margin-right: ${this.margin};"
|
||||
>
|
||||
<div
|
||||
id="${this.rightContainerId}"
|
||||
class="w-full h-full flex items-center justify-center p-2"
|
||||
>
|
||||
${!this.adLoaded
|
||||
? html`<span class="text-white text-xs text-center"
|
||||
>Loading ad...</span
|
||||
>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
+36
-5
@@ -11,7 +11,7 @@ import "./FlagInput";
|
||||
import { FlagInput } from "./FlagInput";
|
||||
import { GameStartingModal } from "./GameStartingModal";
|
||||
import "./GoogleAdElement";
|
||||
import GoogleAdElement from "./GoogleAdElement";
|
||||
import { GutterAdModal } from "./GutterAdModal";
|
||||
import { HelpModal } from "./HelpModal";
|
||||
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
|
||||
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
|
||||
@@ -34,6 +34,26 @@ import "./components/baseComponents/Modal";
|
||||
import { discordLogin, getUserMe, isLoggedIn, logOut } from "./jwt";
|
||||
import "./styles.css";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
PageOS: {
|
||||
session: {
|
||||
newPageView: () => void;
|
||||
};
|
||||
};
|
||||
ramp: {
|
||||
que: Array<() => void>;
|
||||
passiveMode: boolean;
|
||||
spaAddAds: (ads: Array<{ type: string; selectorId: string }>) => void;
|
||||
destroyUnits: (adType: string) => void;
|
||||
settings?: {
|
||||
slots?: any;
|
||||
};
|
||||
spaNewPage: (url: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface JoinLobbyEvent {
|
||||
clientID: string;
|
||||
// Multiplayer games only have gameID, gameConfig is not known until game starts.
|
||||
@@ -53,8 +73,8 @@ class Client {
|
||||
|
||||
private joinModal: JoinPrivateLobbyModal;
|
||||
private publicLobby: PublicLobby;
|
||||
private googleAds: NodeListOf<GoogleAdElement>;
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
private gutterAdModal: GutterAdModal;
|
||||
|
||||
constructor() {}
|
||||
|
||||
@@ -122,9 +142,6 @@ class Client {
|
||||
}
|
||||
|
||||
this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
|
||||
this.googleAds = document.querySelectorAll(
|
||||
"google-ad",
|
||||
) as NodeListOf<GoogleAdElement>;
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
console.log("Browser is closing");
|
||||
@@ -149,6 +166,12 @@ class Client {
|
||||
}
|
||||
});
|
||||
|
||||
this.gutterAdModal = document.querySelector(
|
||||
"gutter-ad-modal",
|
||||
) as GutterAdModal;
|
||||
this.gutterAdModal instanceof GutterAdModal;
|
||||
this.gutterAdModal.show();
|
||||
|
||||
// const ctModal = document.querySelector("chat-modal") as ChatModal;
|
||||
// ctModal instanceof ChatModal;
|
||||
// document.getElementById("chat-button").addEventListener("click", () => {
|
||||
@@ -350,6 +373,14 @@ class Client {
|
||||
() => {
|
||||
this.joinModal.close();
|
||||
this.publicLobby.stop();
|
||||
this.gutterAdModal.hide();
|
||||
|
||||
try {
|
||||
window.PageOS.session.newPageView();
|
||||
} catch (e) {
|
||||
console.error("Error calling newPageView", e);
|
||||
}
|
||||
|
||||
document.querySelectorAll(".ad").forEach((ad) => {
|
||||
(ad as HTMLElement).style.display = "none";
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
||||
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
|
||||
import { Layer } from "./layers/Layer";
|
||||
import { Leaderboard } from "./layers/Leaderboard";
|
||||
import { LeftInGameAd } from "./layers/LeftInGameAd";
|
||||
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
||||
import { MultiTabModal } from "./layers/MultiTabModal";
|
||||
import { NameLayer } from "./layers/NameLayer";
|
||||
@@ -205,6 +206,14 @@ export function createRenderer(
|
||||
unitInfoModal.structureLayer = structureLayer;
|
||||
// unitInfoModal.eventBus = eventBus;
|
||||
|
||||
const leftInGameAd = document.querySelector(
|
||||
"left-in-game-ad",
|
||||
) as LeftInGameAd;
|
||||
if (!(leftInGameAd instanceof LeftInGameAd)) {
|
||||
console.error("left in game ad not found");
|
||||
}
|
||||
leftInGameAd.g = game;
|
||||
|
||||
const layers: Layer[] = [
|
||||
new TerrainLayer(game, transformHandler),
|
||||
new TerritoryLayer(game, eventBus, transformHandler),
|
||||
@@ -241,6 +250,7 @@ export function createRenderer(
|
||||
headsUpMessage,
|
||||
unitInfoModal,
|
||||
multiTabModal,
|
||||
leftInGameAd,
|
||||
];
|
||||
|
||||
return new GameRenderer(
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const BREAKPOINT = {
|
||||
width: 1000,
|
||||
height: 800,
|
||||
};
|
||||
|
||||
const AD_TYPE = "standard_iab_modl2"; // 320x50/320x100 - better for bottom positioning
|
||||
const AD_CONTAINER_ID = "bottom-left-ad-container";
|
||||
|
||||
@customElement("left-in-game-ad")
|
||||
export class LeftInGameAd extends LitElement implements Layer {
|
||||
public g: GameView;
|
||||
|
||||
@state()
|
||||
private isVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private adLoaded: boolean = false;
|
||||
|
||||
// Override createRenderRoot to disable shadow DOM
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
static styles = css``;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
// Load the ad when showing (with small delay to ensure DOM is ready)
|
||||
setTimeout(() => this.loadAd(), 100);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.isVisible = false;
|
||||
this.adLoaded = false;
|
||||
this.requestUpdate();
|
||||
// Destroy the ad when hiding
|
||||
this.destroyAd();
|
||||
}
|
||||
|
||||
public async tick() {
|
||||
if (!this.isVisible && !this.g.inSpawnPhase() && this.screenLargeEnough()) {
|
||||
console.log("showing bottom left ad");
|
||||
this.show();
|
||||
}
|
||||
if (this.isVisible && !this.screenLargeEnough()) {
|
||||
console.log("hiding bottom left ad");
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private screenLargeEnough(): boolean {
|
||||
return (
|
||||
window.innerWidth > BREAKPOINT.width &&
|
||||
window.innerHeight > BREAKPOINT.height
|
||||
);
|
||||
}
|
||||
|
||||
private loadAd(): void {
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available");
|
||||
return;
|
||||
}
|
||||
if (this.adLoaded) {
|
||||
console.log("Ad already loaded, skipping");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.ramp.que.push(() => {
|
||||
window.ramp.spaAddAds([
|
||||
{
|
||||
type: AD_TYPE,
|
||||
selectorId: AD_CONTAINER_ID,
|
||||
},
|
||||
]);
|
||||
this.adLoaded = true;
|
||||
console.log("Playwire ad loaded:", AD_TYPE);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load Playwire ad:", error);
|
||||
}
|
||||
}
|
||||
|
||||
private destroyAd(): void {
|
||||
if (!window.ramp || !this.adLoaded) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.ramp.que.push(() => {
|
||||
window.ramp.destroyUnits(AD_TYPE);
|
||||
console.log("Playwire ad destroyed:", AD_TYPE);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to destroy Playwire ad:", error);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
// Clean up ad when component is removed
|
||||
this.destroyAd();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="w-full min-h-[100px] bg-gray-900 border border-gray-600 z-[9999] pointer-events-auto flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
id="${AD_CONTAINER_ID}"
|
||||
class="w-full h-full flex items-center justify-center"
|
||||
>
|
||||
${!this.adLoaded
|
||||
? html`<span class="text-white text-sm">Loading ad...</span>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
+13
-55
@@ -45,61 +45,6 @@
|
||||
filter: blur(4px) brightness(0.7);
|
||||
}
|
||||
|
||||
.left-gutter-ad {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 200px;
|
||||
/* Changed from top: 50% */
|
||||
transform: none;
|
||||
/* Removed translateY(-50%) since we don't need to center anymore */
|
||||
z-index: 40;
|
||||
width: 300px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.left-gutter-ad google-ad {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.right-gutter-ad {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 200px;
|
||||
/* Changed from top: 50% */
|
||||
transform: none;
|
||||
/* Removed translateY(-50%) since we don't need to center anymore */
|
||||
z-index: 40;
|
||||
width: 300px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.right-gutter-ad google-ad {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Media query to hide the gutter ad on smaller screens */
|
||||
@media (max-width: 1280px) {
|
||||
.left-gutter-ad {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.right-gutter-ad {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* display:none if child has class parent-hidden since we can't use shadow DOM in Lit due to Tailwind */
|
||||
.component-hideable:has(> .parent-hidden) {
|
||||
display: none;
|
||||
@@ -111,6 +56,13 @@
|
||||
document.documentElement.className = "preload";
|
||||
</script>
|
||||
|
||||
<!-- Playwire ads -->
|
||||
<script>
|
||||
window.ramp = window.ramp || {};
|
||||
window.ramp.que = window.ramp.que || [];
|
||||
window.ramp.passiveMode = true;
|
||||
</script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
async
|
||||
@@ -210,6 +162,8 @@
|
||||
</header>
|
||||
<div class="bg-image"></div>
|
||||
|
||||
<gutter-ad-modal></gutter-ad-modal>
|
||||
|
||||
<!-- Main container with responsive padding -->
|
||||
<main class="flex justify-center flex-grow">
|
||||
<div class="container pt-12">
|
||||
@@ -328,6 +282,7 @@
|
||||
<events-display></events-display>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/3 md:max-w-72" style="pointer-events: auto">
|
||||
<left-in-game-ad></left-in-game-ad>
|
||||
<control-panel></control-panel>
|
||||
</div>
|
||||
</div>
|
||||
@@ -442,6 +397,9 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Playwire ads -->
|
||||
<script async src="//cdn.intergient.com/1025558/75940/ramp.js"></script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
defer
|
||||
|
||||
Reference in New Issue
Block a user