mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:00:44 +00:00
Add enzo video tutorial in the help modal (#3059)
## Description: Add video at top of help section, also show a glowing dot for new players. <img width="301" height="133" alt="Screenshot 2026-01-28 at 7 25 23 PM" src="https://github.com/user-attachments/assets/c6b01853-f066-470f-a22d-8995fd81fe0f" /> ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -59,6 +59,8 @@
|
||||
"title": "Release Notes"
|
||||
},
|
||||
"help_modal": {
|
||||
"video_tutorial": "Video Tutorial",
|
||||
"video_tutorial_title": "OpenFront.io Tutorial",
|
||||
"hotkeys": "Hotkeys",
|
||||
"table_key": "Key",
|
||||
"table_action": "Action",
|
||||
|
||||
+55
-2
@@ -1,6 +1,6 @@
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { translateText } from "../client/Utils";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { translateText, TUTORIAL_VIDEO_URL } from "../client/Utils";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/Difficulties";
|
||||
import "./components/Maps";
|
||||
@@ -9,6 +9,7 @@ import { modalHeader } from "./components/ui/ModalHeader";
|
||||
@customElement("help-modal")
|
||||
export class HelpModal extends BaseModal {
|
||||
@state() private keybinds: Record<string, string> = this.getKeybinds();
|
||||
@query("#tutorial-video-iframe") private videoIframe?: HTMLIFrameElement;
|
||||
|
||||
private isKeybindObject(v: unknown): v is { value: string } {
|
||||
return (
|
||||
@@ -120,6 +121,47 @@ export class HelpModal extends BaseModal {
|
||||
[&_p]:text-gray-300 [&_p]:mb-3 [&_strong]:text-white [&_strong]:font-bold
|
||||
scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent"
|
||||
>
|
||||
<!-- Video Tutorial Section -->
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="text-blue-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<h3
|
||||
class="text-xl font-bold uppercase tracking-widest text-white/90"
|
||||
>
|
||||
${translateText("help_modal.video_tutorial")}
|
||||
</h3>
|
||||
<div
|
||||
class="flex-1 h-px bg-gradient-to-r from-blue-500/50 to-transparent"
|
||||
></div>
|
||||
</div>
|
||||
<section
|
||||
class="bg-white/5 rounded-xl border border-white/10 overflow-hidden mb-8"
|
||||
>
|
||||
<div class="relative w-full h-0 pb-[56.25%]">
|
||||
<iframe
|
||||
id="tutorial-video-iframe"
|
||||
class="absolute top-0 left-0 w-full h-full"
|
||||
src="${TUTORIAL_VIDEO_URL}"
|
||||
title="${translateText("help_modal.video_tutorial_title")}"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Hotkeys Section -->
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="text-blue-400">
|
||||
@@ -1139,5 +1181,16 @@ export class HelpModal extends BaseModal {
|
||||
|
||||
protected onOpen(): void {
|
||||
this.keybinds = this.getKeybinds();
|
||||
// Restore the video src when modal opens
|
||||
if (this.videoIframe) {
|
||||
this.videoIframe.src = TUTORIAL_VIDEO_URL;
|
||||
}
|
||||
}
|
||||
|
||||
protected onClose(): void {
|
||||
// Clear the iframe src to stop video playback
|
||||
if (this.videoIframe) {
|
||||
this.videoIframe.src = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import IntlMessageFormat from "intl-messageformat";
|
||||
import { MessageType } from "../core/game/Game";
|
||||
import type { LangSelector } from "./LangSelector";
|
||||
|
||||
export const TUTORIAL_VIDEO_URL = "https://www.youtube.com/embed/EN2oOog3pSs";
|
||||
|
||||
export function renderDuration(totalSeconds: number): string {
|
||||
if (totalSeconds <= 0) return "0s";
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { getGamesPlayed } from "../Utils";
|
||||
|
||||
const HELP_SEEN_KEY = "helpSeen";
|
||||
|
||||
@customElement("desktop-nav-bar")
|
||||
export class DesktopNavBar extends LitElement {
|
||||
@state() private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true";
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@@ -40,6 +45,15 @@ export class DesktopNavBar extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private showHelpDot(): boolean {
|
||||
return getGamesPlayed() < 10 && !this._helpSeen;
|
||||
}
|
||||
|
||||
private onHelpClick = () => {
|
||||
localStorage.setItem(HELP_SEEN_KEY, "true");
|
||||
this._helpSeen = true;
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<nav
|
||||
@@ -125,11 +139,24 @@ export class DesktopNavBar extends LitElement {
|
||||
data-page="page-stats"
|
||||
data-i18n="main.stats"
|
||||
></button>
|
||||
<button
|
||||
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
|
||||
data-page="page-help"
|
||||
data-i18n="main.help"
|
||||
></button>
|
||||
<div class="relative">
|
||||
<button
|
||||
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
|
||||
data-page="page-help"
|
||||
data-i18n="main.help"
|
||||
@click=${this.onHelpClick}
|
||||
></button>
|
||||
${this.showHelpDot()
|
||||
? html`
|
||||
<span
|
||||
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-400 rounded-full animate-ping"
|
||||
></span>
|
||||
<span
|
||||
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-400 rounded-full"
|
||||
></span>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<lang-selector></lang-selector>
|
||||
<button
|
||||
id="nav-account-button"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import {
|
||||
getGamesPlayed,
|
||||
isInIframe,
|
||||
translateText,
|
||||
TUTORIAL_VIDEO_URL,
|
||||
} from "../../../client/Utils";
|
||||
import { ColorPalette, Pattern } from "../../../core/CosmeticSchemas";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
@@ -131,9 +132,7 @@ export class WinModal extends LitElement implements Layer {
|
||||
<div class="relative w-full pb-[56.25%]">
|
||||
<iframe
|
||||
class="absolute top-0 left-0 w-full h-full rounded-sm"
|
||||
src="${this.isVisible
|
||||
? "https://www.youtube.com/embed/EN2oOog3pSs"
|
||||
: ""}"
|
||||
src="${this.isVisible ? TUTORIAL_VIDEO_URL : ""}"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
|
||||
Reference in New Issue
Block a user