mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:50:43 +00:00
Merge branch 'v30'
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"lang_code": "en"
|
||||
},
|
||||
"common": {
|
||||
"not_logged_in": "Not logged in",
|
||||
"close": "Close",
|
||||
"copy": "Copy",
|
||||
"paste": "Paste",
|
||||
@@ -939,7 +940,6 @@
|
||||
"territory_patterns": {
|
||||
"title": "Skins",
|
||||
"purchase": "Purchase",
|
||||
"not_logged_in": "Not logged in",
|
||||
"pattern": {
|
||||
"default": "Default"
|
||||
},
|
||||
|
||||
@@ -25,6 +25,16 @@ declare global {
|
||||
},
|
||||
) => void;
|
||||
};
|
||||
banner: {
|
||||
requestBanner: (options: {
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}) => Promise<void>;
|
||||
requestResponsiveBanner: (containerId: string) => Promise<void>;
|
||||
clearBanner: (containerId: string) => void;
|
||||
clearAllBanners: () => void;
|
||||
};
|
||||
game: {
|
||||
gameplayStart: () => Promise<void>;
|
||||
gameplayStop: () => Promise<void>;
|
||||
@@ -76,11 +86,9 @@ export class CrazyGamesSDK {
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.log("[CrazyGames]: ", e);
|
||||
// If we get a cross-origin error, we're definitely iframed
|
||||
// Check our own referrer as fallback
|
||||
const isCrazyGames = document.referrer.includes("crazygames");
|
||||
console.log("[CrazyGames], contains referrer: ", isCrazyGames);
|
||||
if (isCrazyGames) {
|
||||
return true;
|
||||
}
|
||||
@@ -323,6 +331,70 @@ export class CrazyGamesSDK {
|
||||
}
|
||||
}
|
||||
|
||||
private bottomLeftContainerId = "cg-bottom-left-ad";
|
||||
private bottomLeftAdVisible = false;
|
||||
|
||||
createBottomLeftAd(): void {
|
||||
console.log(
|
||||
`[CrazyGames] createBottomLeftAd called, isReady=${this.isReady()}`,
|
||||
);
|
||||
if (!this.isReady()) {
|
||||
console.log("[CrazyGames] SDK not ready, skipping bottom-left ad");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.bottomLeftAdVisible) {
|
||||
console.log("[CrazyGames] Bottom-left ad already visible");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existing container if any
|
||||
document.getElementById(this.bottomLeftContainerId)?.remove();
|
||||
|
||||
const container = document.createElement("div");
|
||||
container.id = this.bottomLeftContainerId;
|
||||
container.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 300px;
|
||||
height: 250px;
|
||||
z-index: 9999;
|
||||
pointer-events: auto;
|
||||
`;
|
||||
document.body.appendChild(container);
|
||||
console.log("[CrazyGames] Created bottom-left ad container");
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await window.CrazyGames!.SDK.banner.requestBanner({
|
||||
id: this.bottomLeftContainerId,
|
||||
width: 300,
|
||||
height: 250,
|
||||
});
|
||||
console.log("[CrazyGames] Bottom-left banner loaded");
|
||||
} catch (e) {
|
||||
console.log("[CrazyGames] Bottom-left banner error:", e);
|
||||
}
|
||||
})();
|
||||
|
||||
this.bottomLeftAdVisible = true;
|
||||
}
|
||||
|
||||
clearBottomLeftAd(): void {
|
||||
if (!this.bottomLeftAdVisible) return;
|
||||
|
||||
try {
|
||||
window.CrazyGames!.SDK.banner.clearBanner(this.bottomLeftContainerId);
|
||||
} catch (e) {
|
||||
console.error("[CrazyGames] Error clearing bottom-left banner:", e);
|
||||
}
|
||||
|
||||
document.getElementById(this.bottomLeftContainerId)?.remove();
|
||||
this.bottomLeftAdVisible = false;
|
||||
console.log("[CrazyGames] Bottom-left ad cleared");
|
||||
}
|
||||
|
||||
requestMidgameAd(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.isReady()) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { fetchCosmetics, flagRelationship } from "./Cosmetics";
|
||||
import { translateText } from "./Utils";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/FlagButton";
|
||||
import "./components/NotLoggedInWarning";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
|
||||
@customElement("flag-input-modal")
|
||||
@@ -104,6 +105,7 @@ export class FlagInputModal extends BaseModal {
|
||||
title: translateText("flag_input.title"),
|
||||
onBack: () => this.close(),
|
||||
ariaLabel: translateText("common.back"),
|
||||
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
|
||||
})}
|
||||
|
||||
<div class="md:flex items-center gap-2 justify-center mt-4">
|
||||
@@ -119,6 +121,17 @@ export class FlagInputModal extends BaseModal {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center py-3 shrink-0">
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-bold uppercase tracking-wider rounded-lg bg-blue-600 hover:bg-blue-700 text-white cursor-pointer transition-colors"
|
||||
@click=${() => {
|
||||
this.close();
|
||||
window.showPage?.("page-item-store");
|
||||
}}
|
||||
>
|
||||
${translateText("main.store")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 overflow-y-auto px-3 pb-3 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent mr-1"
|
||||
|
||||
+2
-10
@@ -101,17 +101,9 @@ function updateAccountNavButton(userMeResponse: UserMeResponse | false) {
|
||||
// If the avatar fails to load (bad URL / CDN issue / offline), fall back
|
||||
// to the default sign-in UI instead of leaving a broken image.
|
||||
avatarEl.onerror = () => {
|
||||
// Only handle if this is the latest update
|
||||
if (avatarEl._navToken !== navToken) return;
|
||||
avatarEl.src = "";
|
||||
// If the user is still logged in via email, show the email badge state.
|
||||
const email =
|
||||
userMeResponse !== false ? userMeResponse.user.email : undefined;
|
||||
if (email) {
|
||||
showEmailLoggedIn();
|
||||
} else {
|
||||
showSignIn();
|
||||
}
|
||||
avatarEl.onerror = null;
|
||||
avatarEl.src = "https://cdn.discordapp.com/embed/avatars/0.png";
|
||||
};
|
||||
avatarEl.onload = () => {
|
||||
// Only handle if this is the latest update
|
||||
|
||||
@@ -19,11 +19,20 @@ export function initNavigation() {
|
||||
// Close mobile sidebar if a nav item was clicked
|
||||
closeMobileSidebar();
|
||||
|
||||
// Hide only the currently visible modal
|
||||
// Close the currently visible modal properly
|
||||
const visibleModal = document.querySelector(".page-content:not(.hidden)");
|
||||
if (visibleModal) {
|
||||
visibleModal.classList.add("hidden");
|
||||
visibleModal.classList.remove("block");
|
||||
// If it's an open modal component, call close() for proper cleanup (onClose callback, etc.)
|
||||
if (
|
||||
typeof (visibleModal as any).isOpen === "function" &&
|
||||
(visibleModal as any).isOpen() &&
|
||||
typeof (visibleModal as any).close === "function"
|
||||
) {
|
||||
(visibleModal as any).close();
|
||||
} else {
|
||||
visibleModal.classList.add("hidden");
|
||||
visibleModal.classList.remove("block");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle page-play separately (it's not a page-content element)
|
||||
|
||||
@@ -145,14 +145,7 @@ export class SinglePlayerModal extends BaseModal {
|
||||
return;
|
||||
}
|
||||
|
||||
const achievements = Array.isArray(userMe.player.achievements)
|
||||
? userMe.player.achievements
|
||||
: [];
|
||||
|
||||
const completions =
|
||||
achievements.find(
|
||||
(achievement) => achievement?.type === "singleplayer-map",
|
||||
)?.data ?? [];
|
||||
const completions = userMe.player.achievements.singleplayerMap;
|
||||
|
||||
const winsMap = new Map<GameMapType, Set<Difficulty>>();
|
||||
for (const entry of completions) {
|
||||
|
||||
+2
-18
@@ -5,9 +5,9 @@ import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { ColorPalette, Cosmetics, Pattern } from "../core/CosmeticSchemas";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { PlayerPattern } from "../core/Schemas";
|
||||
import { hasLinkedAccount } from "./Api";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/FlagButton";
|
||||
import "./components/NotLoggedInWarning";
|
||||
import "./components/PatternButton";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
import {
|
||||
@@ -77,11 +77,7 @@ export class StoreModal extends BaseModal {
|
||||
title: translateText("store.title"),
|
||||
onBack: () => this.close(),
|
||||
ariaLabel: translateText("common.back"),
|
||||
rightContent: !hasLinkedAccount(this.userMeResponse)
|
||||
? html`<div class="flex items-center">
|
||||
${this.renderNotLoggedInWarning()}
|
||||
</div>`
|
||||
: undefined,
|
||||
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
|
||||
})}
|
||||
<div class="flex items-center gap-2 justify-center pt-2">
|
||||
<button
|
||||
@@ -214,18 +210,6 @@ export class StoreModal extends BaseModal {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderNotLoggedInWarning(): TemplateResult {
|
||||
return html`<button
|
||||
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
|
||||
@click=${() => {
|
||||
this.close();
|
||||
window.showPage?.("page-account");
|
||||
}}
|
||||
>
|
||||
${translateText("territory_patterns.not_logged_in")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isActive && !this.inline) return html``;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { Cosmetics, Pattern } from "../core/CosmeticSchemas";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { PlayerPattern } from "../core/Schemas";
|
||||
import { hasLinkedAccount } from "./Api";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/NotLoggedInWarning";
|
||||
import "./components/PatternButton";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
import {
|
||||
@@ -123,18 +123,6 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderNotLoggedInWarning(): TemplateResult {
|
||||
return html`<button
|
||||
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
|
||||
@click=${() => {
|
||||
this.close();
|
||||
window.showPage?.("page-account");
|
||||
}}
|
||||
>
|
||||
${translateText("territory_patterns.not_logged_in")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = html`
|
||||
<div class="${this.modalContainerClass}">
|
||||
@@ -145,13 +133,20 @@ export class TerritoryPatternsModal extends BaseModal {
|
||||
title: translateText("territory_patterns.title"),
|
||||
onBack: () => this.close(),
|
||||
ariaLabel: translateText("common.back"),
|
||||
rightContent: !hasLinkedAccount(this.userMeResponse)
|
||||
? html`<div class="flex items-center">
|
||||
${this.renderNotLoggedInWarning()}
|
||||
</div>`
|
||||
: undefined,
|
||||
rightContent: html`<not-logged-in-warning></not-logged-in-warning>`,
|
||||
})}
|
||||
</div>
|
||||
<div class="flex justify-center py-3 shrink-0">
|
||||
<button
|
||||
class="px-4 py-2 text-sm font-bold uppercase tracking-wider rounded-lg bg-blue-600 hover:bg-blue-700 text-white cursor-pointer transition-colors"
|
||||
@click=${() => {
|
||||
this.close();
|
||||
window.showPage?.("page-item-store");
|
||||
}}
|
||||
>
|
||||
${translateText("main.store")}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 overflow-y-auto px-3 pb-3 scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent mr-1"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { UserMeResponse } from "../../core/ApiSchemas";
|
||||
import { hasLinkedAccount } from "../Api";
|
||||
|
||||
@customElement("not-logged-in-warning")
|
||||
export class NotLoggedInWarning extends LitElement {
|
||||
@state() private linked = false;
|
||||
|
||||
private _onUserMe = (event: CustomEvent<UserMeResponse | false>) => {
|
||||
this.linked = hasLinkedAccount(event.detail);
|
||||
};
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
document.addEventListener(
|
||||
"userMeResponse",
|
||||
this._onUserMe as EventListener,
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener(
|
||||
"userMeResponse",
|
||||
this._onUserMe as EventListener,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.linked) return html``;
|
||||
|
||||
return html`<div class="flex items-center">
|
||||
<button
|
||||
class="px-4 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-red-500/20 text-red-400 border border-red-500/30 cursor-pointer hover:bg-red-500/30"
|
||||
data-i18n="common.not_logged_in"
|
||||
@click=${() => {
|
||||
window.showPage?.("page-account");
|
||||
}}
|
||||
>
|
||||
Not logged in
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,20 @@ export class DiscordUserHeader extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const defaultAvatar = "https://cdn.discordapp.com/embed/avatars/0.png";
|
||||
const imgSrc = this.avatarUrl ?? defaultAvatar;
|
||||
return html`
|
||||
<div class="flex items-center gap-2">
|
||||
${this.avatarUrl
|
||||
${this._data
|
||||
? html`
|
||||
<div class="p-[3px] rounded-full bg-gray-500">
|
||||
<img
|
||||
class="w-12 h-12 rounded-full block"
|
||||
src="${this.avatarUrl}"
|
||||
src="${imgSrc}"
|
||||
alt="${translateText("discord_user_header.avatar_alt")}"
|
||||
@error=${(e: Event) => {
|
||||
(e.target as HTMLImageElement).src = defaultAvatar;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { crazyGamesSDK } from "../../CrazyGamesSDK";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const AD_TYPE = "standard_iab_left1";
|
||||
@@ -30,6 +31,7 @@ export class InGamePromo extends LitElement implements Layer {
|
||||
}
|
||||
if (!this.cornerAdShown) {
|
||||
this.cornerAdShown = true;
|
||||
console.log("[InGamePromo] Spawn phase ended, triggering showAd");
|
||||
this.showAd();
|
||||
}
|
||||
}
|
||||
@@ -73,10 +75,19 @@ export class InGamePromo extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
private showAd(): void {
|
||||
if (!window.adsEnabled) return;
|
||||
console.log(
|
||||
`[InGamePromo] showAd called, isOnCrazyGames=${crazyGamesSDK.isOnCrazyGames()}`,
|
||||
);
|
||||
if (window.innerWidth < 1100) return;
|
||||
if (window.innerHeight < 750) return;
|
||||
|
||||
if (crazyGamesSDK.isOnCrazyGames()) {
|
||||
this.showCrazyGamesAd();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.adsEnabled) return;
|
||||
|
||||
this.shouldShow = true;
|
||||
this.requestUpdate();
|
||||
|
||||
@@ -85,6 +96,25 @@ export class InGamePromo extends LitElement implements Layer {
|
||||
});
|
||||
}
|
||||
|
||||
private showCrazyGamesAd(): void {
|
||||
console.log(
|
||||
`[InGamePromo] showCrazyGamesAd called, isReady=${crazyGamesSDK.isReady()}, width=${window.innerWidth}, height=${window.innerHeight}`,
|
||||
);
|
||||
if (!crazyGamesSDK.isReady()) {
|
||||
console.log(
|
||||
"[InGamePromo] CrazyGames SDK not ready, skipping in-game ad",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.requestUpdate();
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
console.log("[InGamePromo] DOM updated, calling createBottomLeftAd");
|
||||
crazyGamesSDK.createBottomLeftAd();
|
||||
});
|
||||
}
|
||||
|
||||
private loadAd(): void {
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available for in-game ad");
|
||||
@@ -112,6 +142,14 @@ export class InGamePromo extends LitElement implements Layer {
|
||||
|
||||
public hideAd(): void {
|
||||
this.destroyBottomRail();
|
||||
|
||||
if (crazyGamesSDK.isOnCrazyGames()) {
|
||||
crazyGamesSDK.clearBottomLeftAd();
|
||||
this.shouldShow = false;
|
||||
this.requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available for in-game ad");
|
||||
return;
|
||||
|
||||
@@ -69,14 +69,9 @@ export const UserMeResponseSchema = z.object({
|
||||
publicId: z.string(),
|
||||
roles: z.string().array().optional(),
|
||||
flares: z.string().array().optional(),
|
||||
achievements: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z.literal("singleplayer-map"), // TODO: change the shape to be more flexible when we have more achievements
|
||||
data: z.array(SingleplayerMapAchievementSchema),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
achievements: z.object({
|
||||
singleplayerMap: z.array(SingleplayerMapAchievementSchema),
|
||||
}),
|
||||
leaderboard: z
|
||||
.object({
|
||||
oneVone: z
|
||||
|
||||
Reference in New Issue
Block a user