From 8b5cd160b6a3c549cf33b674ddc25b7fcf64eefb Mon Sep 17 00:00:00 2001 From: tobspr Date: Wed, 25 Aug 2021 16:56:52 +0200 Subject: [PATCH] DLC: Next Puzzle button & Search functionality, other stuff --- .../puzzle_complete_notification.scss | 8 +- src/css/ingame_hud/puzzle_next.scss | 41 ++++ src/css/main.scss | 3 + src/css/states/puzzle_menu.scss | 67 ++++- src/css/states/settings.scss | 10 +- src/js/core/config.js | 2 + src/js/game/hud/parts/HUDPuzzleNextPuzzle.js | 25 ++ .../hud/parts/puzzle_complete_notification.js | 27 +- src/js/game/modes/puzzle_play.js | 13 +- src/js/platform/api.js | 16 +- src/js/savegame/savegame.js | 18 ++ src/js/states/puzzle_menu.js | 230 +++++++++++++++--- src/js/states/settings.js | 10 + translations/base-en.yaml | 18 ++ 14 files changed, 437 insertions(+), 51 deletions(-) create mode 100644 src/css/ingame_hud/puzzle_next.scss create mode 100644 src/js/game/hud/parts/HUDPuzzleNextPuzzle.js diff --git a/src/css/ingame_hud/puzzle_complete_notification.scss b/src/css/ingame_hud/puzzle_complete_notification.scss index 5f36df82..a35da83d 100644 --- a/src/css/ingame_hud/puzzle_complete_notification.scss +++ b/src/css/ingame_hud/puzzle_complete_notification.scss @@ -137,16 +137,20 @@ button.continue { background: #555; - @include S(margin-right, 10px); } button.menu { + background: #555; + } + + button.nextPuzzle { background-color: $colorGreenBright; } > button { @include S(min-width, 100px); - @include S(padding, 10px, 20px); + @include S(padding, 8px, 16px); + @include S(margin, 0, 6px); @include IncreasedClickArea(0px); } } diff --git a/src/css/ingame_hud/puzzle_next.scss b/src/css/ingame_hud/puzzle_next.scss new file mode 100644 index 00000000..ee0f664f --- /dev/null +++ b/src/css/ingame_hud/puzzle_next.scss @@ -0,0 +1,41 @@ +#ingame_HUD_PuzzleNextPuzzle { + position: absolute; + @include S(top, 17px); + @include S(right, 10px); + + display: flex; + flex-direction: column; + align-items: flex-end; + backdrop-filter: blur(D(1px)); + padding: D(3px); + + > .button { + @include ButtonText; + @include IncreasedClickArea(0px); + pointer-events: all; + cursor: pointer; + position: relative; + color: #333438; + transition: all 0.12s ease-in-out; + text-transform: uppercase; + transition-property: opacity, transform; + @include PlainText; + @include S(padding-right, 25px); + opacity: 1; + + @include DarkThemeInvert; + + &:hover { + opacity: 0.9 !important; + } + + &.pressed { + transform: scale(0.95) !important; + } + + & { + /* @load-async */ + background: uiResource("icons/state_next_button.png") right center / D(15px) no-repeat; + } + } +} diff --git a/src/css/main.scss b/src/css/main.scss index 7419a5df..1ac4f537 100644 --- a/src/css/main.scss +++ b/src/css/main.scss @@ -65,6 +65,7 @@ @import "ingame_hud/puzzle_play_settings"; @import "ingame_hud/puzzle_play_metadata"; @import "ingame_hud/puzzle_complete_notification"; +@import "ingame_hud/puzzle_next"; // prettier-ignore $elements: @@ -83,6 +84,7 @@ ingame_HUD_PinnedShapes, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, ingame_HUD_PuzzleBackToMenu, +ingame_HUD_PuzzleNextPuzzle, ingame_HUD_PuzzleEditorReview, ingame_HUD_PuzzleEditorControls, ingame_HUD_PuzzleEditorTitle, @@ -134,6 +136,7 @@ body.uiHidden { #ingame_HUD_GameMenu, #ingame_HUD_PinnedShapes, #ingame_HUD_PuzzleBackToMenu, + #ingame_HUD_PuzzleNextPuzzle, #ingame_HUD_PuzzleEditorReview, #ingame_HUD_Notifications, #ingame_HUD_TutorialHints, diff --git a/src/css/states/puzzle_menu.scss b/src/css/states/puzzle_menu.scss index e2deacf0..0c3e845b 100644 --- a/src/css/states/puzzle_menu.scss +++ b/src/css/states/puzzle_menu.scss @@ -15,8 +15,73 @@ } > .container { + .searchForm { + display: flex; + align-items: center; + justify-content: center; + + color: #333; + background: $accentColorBright; + @include S(padding, 5px); + @include S(border-radius, $globalBorderRadius); + flex-wrap: wrap; + + input.search { + color: #333; + margin: 0; + display: inline-block; + flex-grow: 1; + @include S(padding, 5px, 10px); + @include S(min-width, 50px); + + &::placeholder { + color: #aaa; + } + } + + select { + color: #333; + border: 0; + @include S(padding, 5px); + @include S(border-radius, $globalBorderRadius); + @include S(padding, 7px, 10px); + @include S(margin-left, 5px); + @include PlainText; + } + + .filterCompleted { + @include S(margin-left, 20px); + pointer-events: all; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + text-transform: uppercase; + @include PlainText; + @include S(margin-right, 10px); + + input { + @include S(width, 15px); + @include S(height, 15px); + @include S(margin-right, 5px); + @include S(border-radius, $globalBorderRadius); + border: 0; + } + } + + button[type="submit"] { + @include S(padding, 7px, 10px, 5px); + @include S(margin-left, 20px); + @include S(margin-top, 4px); + @include S(margin-bottom, 4px); + margin-left: auto; + } + } + > .mainContent { overflow: hidden; + display: flex; + flex-direction: column; > .categoryChooser { > .categories { @@ -79,8 +144,8 @@ @include S(grid-gap, 7px); @include S(margin-top, 10px); @include S(padding-right, 4px); - @include S(height, 320px); overflow-y: scroll; + flex-grow: 1; pointer-events: all; position: relative; diff --git a/src/css/states/settings.scss b/src/css/states/settings.scss index 50afcaa3..5b36c677 100644 --- a/src/css/states/settings.scss +++ b/src/css/states/settings.scss @@ -50,7 +50,8 @@ } button.categoryButton, - button.about { + button.about, + button.privacy { background-color: $colorCategoryButton; color: #777a7f; @@ -68,6 +69,10 @@ } } + button.privacy { + @include S(margin-top, 4px); + } + .versionbar { @include S(margin-top, 10px); @@ -180,7 +185,8 @@ .container .content { .sidebar { button.categoryButton, - button.about { + button.about, + button.privacy { color: #ccc; background-color: darken($darkModeControlsBackground, 5); diff --git a/src/js/core/config.js b/src/js/core/config.js index dcb29e77..bc2e2460 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -17,6 +17,8 @@ export const THIRDPARTY_URLS = { reddit: "https://www.reddit.com/r/shapezio", shapeViewer: "https://viewer.shapez.io", + privacyPolicy: "https://tobspr.io/privacy.html", + standaloneStorePage: "https://store.steampowered.com/app/1318690/shapezio/", stanaloneCampaignLink: "https://get.shapez.io", puzzleDlcStorePage: "https://store.steampowered.com/app/1625400/shapezio__Puzzle_DLC", diff --git a/src/js/game/hud/parts/HUDPuzzleNextPuzzle.js b/src/js/game/hud/parts/HUDPuzzleNextPuzzle.js new file mode 100644 index 00000000..f0187d0d --- /dev/null +++ b/src/js/game/hud/parts/HUDPuzzleNextPuzzle.js @@ -0,0 +1,25 @@ +import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { PuzzlePlayGameMode } from "../../modes/puzzle_play"; +import { BaseHUDPart } from "../base_hud_part"; + +export class HUDPuzzleNextPuzzle extends BaseHUDPart { + createElements(parent) { + this.element = makeDiv(parent, "ingame_HUD_PuzzleNextPuzzle"); + this.button = document.createElement("button"); + this.button.classList.add("button"); + this.button.innerText = T.ingame.puzzleCompletion.nextPuzzle; + this.element.appendChild(this.button); + + this.trackClicks(this.button, this.nextPuzzle); + } + + initialize() {} + + nextPuzzle() { + const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode); + this.root.gameState.moveToState("PuzzleMenuState", { + continueQueue: gameMode.nextPuzzles, + }); + } +} diff --git a/src/js/game/hud/parts/puzzle_complete_notification.js b/src/js/game/hud/parts/puzzle_complete_notification.js index f223c1d6..d83e33f1 100644 --- a/src/js/game/hud/parts/puzzle_complete_notification.js +++ b/src/js/game/hud/parts/puzzle_complete_notification.js @@ -6,13 +6,8 @@ import { InputReceiver } from "../../../core/input_receiver"; import { makeDiv } from "../../../core/utils"; import { SOUNDS } from "../../../platform/sound"; import { T } from "../../../translations"; -import { enumColors } from "../../colors"; -import { ColorItem } from "../../items/color_item"; -import { finalGameShape, rocketShape } from "../../modes/regular"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { ShapeItem } from "../../items/shape_item"; -import { ShapeDefinition } from "../../shape_definition"; export class HUDPuzzleCompleteNotification extends BaseHUDPart { initialize() { @@ -68,10 +63,21 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart { this.menuBtn.classList.add("menu", "styledButton"); this.menuBtn.innerText = T.ingame.puzzleCompletion.menuBtn; buttonBar.appendChild(this.menuBtn); - this.trackClicks(this.menuBtn, () => { this.close(true); }); + + const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode); + if (gameMode.nextPuzzles.length > 0) { + this.nextPuzzleBtn = document.createElement("button"); + this.nextPuzzleBtn.classList.add("nextPuzzle", "styledButton"); + this.nextPuzzleBtn.innerText = T.ingame.puzzleCompletion.nextPuzzle; + buttonBar.appendChild(this.nextPuzzleBtn); + + this.trackClicks(this.nextPuzzleBtn, () => { + this.nextPuzzle(); + }); + } } updateState() { @@ -93,6 +99,15 @@ export class HUDPuzzleCompleteNotification extends BaseHUDPart { return this.visible; } + nextPuzzle() { + const gameMode = /** @type {PuzzlePlayGameMode} */ (this.root.gameMode); + gameMode.trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion)).then(() => { + this.root.gameState.moveToState("PuzzleMenuState", { + continueQueue: gameMode.nextPuzzles, + }); + }); + } + close(toMenu) { /** @type {PuzzlePlayGameMode} */ (this.root.gameMode) .trackCompleted(this.userDidLikePuzzle, Math.round(this.timeOfCompletion)) diff --git a/src/js/game/modes/puzzle_play.js b/src/js/game/modes/puzzle_play.js index 46480c51..fc9a8f11 100644 --- a/src/js/game/modes/puzzle_play.js +++ b/src/js/game/modes/puzzle_play.js @@ -30,6 +30,7 @@ import { HUDPuzzlePlaySettings } from "../hud/parts/puzzle_play_settings"; import { MetaBlockBuilding } from "../buildings/block"; import { MetaBuilding } from "../meta_building"; import { gMetaBuildingRegistry } from "../../core/global_registries"; +import { HUDPuzzleNextPuzzle } from "../hud/parts/HUDPuzzleNextPuzzle"; const logger = createLogger("puzzle-play"); const copy = require("clipboard-copy"); @@ -43,8 +44,9 @@ export class PuzzlePlayGameMode extends PuzzleGameMode { * @param {GameRoot} root * @param {object} payload * @param {import("../../savegame/savegame_typedefs").PuzzleFullData} payload.puzzle + * @param {Array | undefined} payload.nextPuzzles */ - constructor(root, { puzzle }) { + constructor(root, { puzzle, nextPuzzles }) { super(root); /** @type {Array} */ @@ -95,6 +97,15 @@ export class PuzzlePlayGameMode extends PuzzleGameMode { root.signals.postLoadHook.add(this.loadPuzzle, this); this.puzzle = puzzle; + + /** + * @type {Array} + */ + this.nextPuzzles = nextPuzzles || []; + + if (this.nextPuzzles.length > 0) { + this.additionalHudParts.puzzleNext = HUDPuzzleNextPuzzle; + } } loadPuzzle() { diff --git a/src/js/platform/api.js b/src/js/platform/api.js index db27360d..d518c98a 100644 --- a/src/js/platform/api.js +++ b/src/js/platform/api.js @@ -143,6 +143,20 @@ export class ClientAPI { return this._request("/v1/puzzles/list/" + category, {}); } + /** + * @param {{ searchTerm: string; difficulty: string; duration: string }} searchOptions + * @returns {Promise} + */ + apiSearchPuzzles(searchOptions) { + if (!this.isLoggedIn()) { + return Promise.reject("not-logged-in"); + } + return this._request("/v1/puzzles/search", { + method: "POST", + body: searchOptions, + }); + } + /** * @param {number} puzzleId * @returns {Promise} @@ -169,7 +183,7 @@ export class ClientAPI { } /** - * @param {number} shortKey + * @param {string} shortKey * @returns {Promise} */ apiDownloadPuzzleByKey(shortKey) { diff --git a/src/js/savegame/savegame.js b/src/js/savegame/savegame.js index 999b90ec..36ed884f 100644 --- a/src/js/savegame/savegame.js +++ b/src/js/savegame/savegame.js @@ -64,6 +64,24 @@ export class Savegame extends ReadWriteProxy { return savegameInterfaces[Savegame.getCurrentVersion()]; } + /** + * + * @param {Application} app + * @returns + */ + static createPuzzleSavegame(app) { + return new Savegame(app, { + internalId: "puzzle", + metaDataRef: { + internalId: "puzzle", + lastUpdate: 0, + version: 0, + level: 0, + name: "puzzle", + }, + }); + } + /** * @returns {number} */ diff --git a/src/js/states/puzzle_menu.js b/src/js/states/puzzle_menu.js index bded8e1e..bdfc28aa 100644 --- a/src/js/states/puzzle_menu.js +++ b/src/js/states/puzzle_menu.js @@ -13,17 +13,30 @@ const navigation = { categories: ["official", "top-rated", "trending", "trending-weekly", "new"], difficulties: ["easy", "medium", "hard"], account: ["mine", "completed"], + search: ["search"], }; const logger = createLogger("puzzle-menu"); let lastCategory = "official"; +let lastSearchOptions = { + searchTerm: "", + difficulty: "any", + duration: "any", + includeCompleted: false, +}; + export class PuzzleMenuState extends TextualGameState { constructor() { super("PuzzleMenuState"); this.loading = false; this.activeCategory = ""; + + /** + * @type {Array} + */ + this.puzzles = []; } getThemeMusic() { @@ -99,13 +112,23 @@ export class PuzzleMenuState extends TextualGameState { activeCategory.classList.remove("active"); } - this.htmlElement.querySelector(`[data-category="${category}"]`).classList.add("active"); + const categoryElement = this.htmlElement.querySelector(`[data-category="${category}"]`); + if (categoryElement) { + categoryElement.classList.add("active"); + } const container = this.htmlElement.querySelector("#mainContainer"); while (container.firstChild) { container.removeChild(container.firstChild); } + if (category === "search") { + this.loading = false; + + this.startSearch(); + return; + } + const loadingElement = document.createElement("div"); loadingElement.classList.add("loader"); loadingElement.innerText = T.global.loading + "..."; @@ -160,23 +183,148 @@ export class PuzzleMenuState extends TextualGameState { } const children = navigation[rootCategory]; - for (const category of children) { - const button = document.createElement("button"); - button.setAttribute("data-category", category); - button.classList.add("styledButton", "category", "child"); - button.innerText = T.puzzleMenu.categories[category]; - this.trackClicks(button, () => this.selectCategory(category)); - subContainer.appendChild(button); + if (children.length > 1) { + for (const category of children) { + const button = document.createElement("button"); + button.setAttribute("data-category", category); + button.classList.add("styledButton", "category", "child"); + button.innerText = T.puzzleMenu.categories[category]; + this.trackClicks(button, () => this.selectCategory(category)); + subContainer.appendChild(button); + } + } + + if (rootCategory === "search") { + this.renderSearchForm(subContainer); } this.selectCategory(subCategory); } + renderSearchForm(parent) { + const container = document.createElement("form"); + container.classList.add("searchForm"); + + // Search + const searchField = document.createElement("input"); + searchField.value = lastSearchOptions.searchTerm; + searchField.classList.add("search"); + searchField.setAttribute("type", "text"); + searchField.setAttribute("placeholder", T.puzzleMenu.search.placeholder); + searchField.addEventListener("input", () => { + lastSearchOptions.searchTerm = searchField.value.trim(); + }); + container.appendChild(searchField); + + // Difficulty + const difficultyFilter = document.createElement("select"); + for (const difficulty of ["any", "easy", "medium", "hard"]) { + const option = document.createElement("option"); + option.value = difficulty; + option.innerText = T.puzzleMenu.search.difficulties[difficulty]; + if (option.value === lastSearchOptions.difficulty) { + option.setAttribute("selected", "selected"); + } + difficultyFilter.appendChild(option); + } + difficultyFilter.addEventListener("change", () => { + const option = difficultyFilter.value; + lastSearchOptions.difficulty = option; + }); + container.appendChild(difficultyFilter); + + // Duration + const durationFilter = document.createElement("select"); + for (const duration of ["any", "short", "medium", "long"]) { + const option = document.createElement("option"); + option.value = duration; + option.innerText = T.puzzleMenu.search.durations[duration]; + if (option.value === lastSearchOptions.duration) { + option.setAttribute("selected", "selected"); + } + durationFilter.appendChild(option); + } + durationFilter.addEventListener("change", () => { + const option = durationFilter.value; + lastSearchOptions.duration = option; + }); + container.appendChild(durationFilter); + + // Include completed + const labelCompleted = document.createElement("label"); + labelCompleted.classList.add("filterCompleted"); + + const inputCompleted = document.createElement("input"); + inputCompleted.setAttribute("type", "checkbox"); + if (lastSearchOptions.includeCompleted) { + inputCompleted.setAttribute("checked", "checked"); + } + inputCompleted.addEventListener("change", () => { + lastSearchOptions.includeCompleted = inputCompleted.checked; + }); + + labelCompleted.appendChild(inputCompleted); + + const text = document.createTextNode(T.puzzleMenu.search.includeCompleted); + labelCompleted.appendChild(text); + + container.appendChild(labelCompleted); + + // Submit + const submitButton = document.createElement("button"); + submitButton.classList.add("styledButton"); + submitButton.setAttribute("type", "submit"); + submitButton.innerText = T.puzzleMenu.search.action; + container.appendChild(submitButton); + + container.addEventListener("submit", event => { + event.preventDefault(); + console.log("Search:", searchField.value.trim()); + this.startSearch(); + }); + + parent.appendChild(container); + } + + startSearch() { + if (this.loading) { + return; + } + + this.loading = true; + + const container = this.htmlElement.querySelector("#mainContainer"); + while (container.firstChild) { + container.removeChild(container.firstChild); + } + + const loadingElement = document.createElement("div"); + loadingElement.classList.add("loader"); + loadingElement.innerText = T.global.loading + "..."; + container.appendChild(loadingElement); + + this.asyncChannel + .watch(this.app.clientApi.apiSearchPuzzles(lastSearchOptions)) + .then( + puzzles => this.renderPuzzles(puzzles), + error => { + this.dialogs.showWarning( + T.dialogs.puzzleLoadFailed.title, + T.dialogs.puzzleLoadFailed.desc + " " + error + ); + this.renderPuzzles([]); + } + ) + .then(() => (this.loading = false)); + } + /** * * @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles */ renderPuzzles(puzzles) { + this.puzzles = puzzles; + const container = this.htmlElement.querySelector("#mainContainer"); while (container.firstChild) { container.removeChild(container.firstChild); @@ -223,15 +371,15 @@ export class PuzzleMenuState extends TextualGameState { difficulty.innerText = completionPercentage + "%"; stats.appendChild(difficulty); - if (completionPercentage < 40) { + if (puzzle.difficulty < 0.2) { + difficulty.classList.add("stage--easy"); + difficulty.innerText = T.puzzleMenu.difficulties.easy; + } else if (puzzle.difficulty > 0.6) { difficulty.classList.add("stage--hard"); difficulty.innerText = T.puzzleMenu.difficulties.hard; - } else if (completionPercentage < 80) { + } else { difficulty.classList.add("stage--medium"); difficulty.innerText = T.puzzleMenu.difficulties.medium; - } else { - difficulty.classList.add("stage--easy"); - difficulty.innerText = T.puzzleMenu.difficulties.easy; } } @@ -275,7 +423,7 @@ export class PuzzleMenuState extends TextualGameState { container.appendChild(elem); - this.trackClicks(elem, () => this.playPuzzle(puzzle)); + this.trackClicks(elem, () => this.playPuzzle(puzzle.id)); } if (puzzles.length === 0) { @@ -328,20 +476,26 @@ export class PuzzleMenuState extends TextualGameState { /** * - * @param {import("../savegame/savegame_typedefs").PuzzleMetadata} puzzle + * @param {number} puzzleId + * @param {Array=} nextPuzzles */ - playPuzzle(puzzle) { + playPuzzle(puzzleId, nextPuzzles) { const closeLoading = this.dialogs.showLoadingDialog(); - this.app.clientApi.apiDownloadPuzzle(puzzle.id).then( + this.asyncChannel.watch(this.app.clientApi.apiDownloadPuzzle(puzzleId)).then( puzzleData => { closeLoading(); - logger.log("Got puzzle:", puzzleData); - this.startLoadedPuzzle(puzzleData); + + nextPuzzles = + nextPuzzles || this.puzzles.filter(puzzle => !puzzle.completed).map(puzzle => puzzle.id); + nextPuzzles = nextPuzzles.filter(id => id !== puzzleId); + + logger.log("Got puzzle:", puzzleData, "next puzzles:", nextPuzzles); + this.startLoadedPuzzle(puzzleData, nextPuzzles); }, err => { closeLoading(); - logger.error("Failed to download puzzle", puzzle.id, ":", err); + logger.error("Failed to download puzzle", puzzleId, ":", err); this.dialogs.showWarning( T.dialogs.puzzleDownloadError.title, T.dialogs.puzzleDownloadError.desc + " " + err @@ -354,18 +508,24 @@ export class PuzzleMenuState extends TextualGameState { * * @param {import("../savegame/savegame_typedefs").PuzzleFullData} puzzle */ - startLoadedPuzzle(puzzle) { - const savegame = this.createEmptySavegame(); + startLoadedPuzzle(puzzle, nextPuzzles) { + const savegame = Savegame.createPuzzleSavegame(this.app); this.moveToState("InGameState", { gameModeId: enumGameModeIds.puzzlePlay, gameModeParameters: { puzzle, + nextPuzzles, }, savegame, }); } onEnter(payload) { + if (payload.continueQueue) { + logger.log("Continuing puzzle queue:", payload); + this.playPuzzle(payload.continueQueue[0], payload.continueQueue.slice(1)); + } + // Find old category let rootCategory = "categories"; for (const [id, children] of Object.entries(navigation)) { @@ -390,26 +550,13 @@ export class PuzzleMenuState extends TextualGameState { this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle()); } - createEmptySavegame() { - return new Savegame(this.app, { - internalId: "puzzle", - metaDataRef: { - internalId: "puzzle", - lastUpdate: 0, - version: 0, - level: 0, - name: "puzzle", - }, - }); - } - loadPuzzle() { const shortKeyInput = new FormElementInput({ id: "shortKey", label: null, placeholder: "", defaultValue: "", - validator: val => ShapeDefinition.isValidShortKey(val), + validator: val => ShapeDefinition.isValidShortKey(val) || val.startsWith("/"), }); const dialog = new DialogWithForm({ @@ -422,9 +569,16 @@ export class PuzzleMenuState extends TextualGameState { this.dialogs.internalShowDialog(dialog); dialog.buttonSignals.ok.add(() => { + const searchTerm = shortKeyInput.getValue(); + + if (searchTerm === "/apikey") { + alert("Your api key is: " + this.app.clientApi.token); + return; + } + const closeLoading = this.dialogs.showLoadingDialog(); - this.app.clientApi.apiDownloadPuzzleByKey(shortKeyInput.getValue()).then( + this.app.clientApi.apiDownloadPuzzleByKey(searchTerm).then( puzzle => { closeLoading(); this.startLoadedPuzzle(puzzle); @@ -451,7 +605,7 @@ export class PuzzleMenuState extends TextualGameState { return; } - const savegame = this.createEmptySavegame(); + const savegame = Savegame.createPuzzleSavegame(this.app); this.moveToState("InGameState", { gameModeId: enumGameModeIds.puzzleEdit, savegame, diff --git a/src/js/states/settings.js b/src/js/states/settings.js index 85b31fbb..23a0b16a 100644 --- a/src/js/states/settings.js +++ b/src/js/states/settings.js @@ -1,3 +1,4 @@ +import { THIRDPARTY_URLS } from "../core/config"; import { TextualGameState } from "../core/textual_game_state"; import { formatSecondsToTimeAgo } from "../core/utils"; import { allApplicationSettings, enumCategories } from "../profile/application_settings"; @@ -34,6 +35,8 @@ export class SettingsState extends TextualGameState { ? "" : ` + + ` }
@@ -109,6 +112,9 @@ export class SettingsState extends TextualGameState { this.trackClicks(this.htmlElement.querySelector(".about"), this.onAboutClicked, { preventDefault: false, }); + this.trackClicks(this.htmlElement.querySelector(".privacy"), this.onPrivacyClicked, { + preventDefault: false, + }); } const keybindingsButton = this.htmlElement.querySelector(".editKeybindings"); @@ -180,6 +186,10 @@ export class SettingsState extends TextualGameState { this.moveToStateAddGoBack("AboutState"); } + onPrivacyClicked() { + this.app.platformWrapper.openExternalLink(THIRDPARTY_URLS.privacyPolicy); + } + onKeybindingsClicked() { this.moveToStateAddGoBack("KeybindingsState"); } diff --git a/translations/base-en.yaml b/translations/base-en.yaml index f15dfc0b..f8ffd96e 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -155,6 +155,23 @@ puzzleMenu: account: My Puzzles search: Search + search: + action: Search + placeholder: Enter a puzzle or author name + includeCompleted: Include Completed + + difficulties: + any: Any Difficulty + easy: Easy + medium: Medium + hard: Hard + + durations: + any: Any Duration + short: Short (< 2 min) + medium: Normal + long: Long (> 10 min) + difficulties: easy: Easy medium: Medium @@ -657,6 +674,7 @@ ingame: continueBtn: Keep Playing menuBtn: Menu + nextPuzzle: Next Puzzle puzzleMetadata: author: Author