diff --git a/src/css/mixins.scss b/src/css/mixins.scss
index 43f7a259..888d84d6 100644
--- a/src/css/mixins.scss
+++ b/src/css/mixins.scss
@@ -15,15 +15,15 @@ $hardwareAcc: null;
// ----------------------------------------
/** Increased click area for this element, helpful on mobile */
@mixin IncreasedClickArea($size) {
- &::after {
- content: "";
- position: absolute;
- top: #{D(-$size)};
- bottom: #{D(-$size)};
- left: #{D(-$size)};
- right: #{D(-$size)};
- // background: rgba(255, 0, 0, 0.3);
- }
+ // &::after {
+ // content: "";
+ // position: absolute;
+ // top: #{D(-$size)};
+ // bottom: #{D(-$size)};
+ // left: #{D(-$size)};
+ // right: #{D(-$size)};
+ // // background: rgba(255, 0, 0, 0.3);
+ // }
}
button,
.increasedClickArea {
diff --git a/src/css/states/puzzle_menu.scss b/src/css/states/puzzle_menu.scss
index 2c0d3773..5f28d902 100644
--- a/src/css/states/puzzle_menu.scss
+++ b/src/css/states/puzzle_menu.scss
@@ -19,42 +19,54 @@
overflow: hidden;
> .categoryChooser {
- display: grid;
- grid-auto-columns: 1fr;
- grid-auto-flow: column;
- @include S(grid-gap, 2px);
- @include S(padding-right, 10px);
+ > .categories {
+ display: grid;
+ grid-auto-columns: 1fr;
+ grid-auto-flow: column;
+ @include S(grid-gap, 2px);
+ @include S(padding-right, 10px);
+ @include S(margin-bottom, 5px);
- > .category {
- background: $accentColorBright;
- border-radius: 0;
- color: $accentColorDark;
- transition: all 0.12s ease-in-out;
- transition-property: opacity, background-color, color;
+ .category {
+ background: $accentColorBright;
+ border-radius: 0;
+ color: $accentColorDark;
+ transition: all 0.12s ease-in-out;
+ transition-property: opacity, background-color, color;
- &:first-child {
- @include S(border-top-left-radius, $globalBorderRadius);
- @include S(border-bottom-left-radius, $globalBorderRadius);
- }
- &:last-child {
- border-top-right-radius: $globalBorderRadius;
- border-bottom-right-radius: $globalBorderRadius;
- }
-
- &.active {
- background: $colorBlueBright;
- opacity: 1 !important;
- color: #fff;
- cursor: default;
- }
-
- @include DarkThemeOverride {
- background: $accentColorDark;
- color: #bbbbc4;
+ &:first-child {
+ @include S(border-top-left-radius, $globalBorderRadius);
+ @include S(border-bottom-left-radius, $globalBorderRadius);
+ }
+ &:last-child {
+ border-top-right-radius: $globalBorderRadius;
+ border-bottom-right-radius: $globalBorderRadius;
+ }
&.active {
background: $colorBlueBright;
+ opacity: 1 !important;
color: #fff;
+ cursor: default;
+ }
+
+ @include DarkThemeOverride {
+ background: $accentColorDark;
+ color: #bbbbc4;
+
+ &.active {
+ background: $colorBlueBright;
+ color: #fff;
+ }
+ }
+
+ &.root {
+ @include S(padding-top, 10px);
+ @include S(padding-bottom, 10px);
+ @include Text;
+ }
+ &.child {
+ @include PlainText;
}
}
}
@@ -62,12 +74,12 @@
> .puzzles {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(D(180px), 1fr));
+ grid-template-columns: repeat(auto-fit, minmax(D(240px), 1fr));
@include S(grid-auto-rows, 65px);
@include S(grid-gap, 7px);
@include S(margin-top, 10px);
@include S(padding-right, 4px);
- @include S(height, 360px);
+ @include S(height, 320px);
overflow-y: scroll;
pointer-events: all;
position: relative;
@@ -203,14 +215,11 @@
font-weight: bold;
@include S(margin-right, 3px);
opacity: 0.7;
+ text-transform: uppercase;
&.stage--easy {
color: $colorGreenBright;
}
- &.stage--normal {
- color: #000;
- @include DarkThemeInvert;
- }
&.stage--medium {
color: $colorOrangeBright;
}
diff --git a/src/js/core/state_manager.js b/src/js/core/state_manager.js
index e0c04bba..2e55f5d4 100644
--- a/src/js/core/state_manager.js
+++ b/src/js/core/state_manager.js
@@ -89,8 +89,13 @@ export class StateManager {
const dialogParent = document.createElement("div");
dialogParent.classList.add("modalDialogParent");
document.body.appendChild(dialogParent);
+ try {
+ this.currentState.internalEnterCallback(payload);
+ } catch (ex) {
+ console.error(ex);
+ throw ex;
+ }
- this.currentState.internalEnterCallback(payload);
this.app.sound.playThemeMusic(this.currentState.getThemeMusic());
this.currentState.onResized(this.app.screenWidth, this.app.screenHeight);
diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js
index d1174020..10abb626 100644
--- a/src/js/states/main_menu.js
+++ b/src/js/states/main_menu.js
@@ -95,7 +95,7 @@ export class MainMenuState extends GameState {
${
- !G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc
+ (!G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc) || G_IS_DEV
? `

${T.puzzleMenu.loadPuzzle}
+
`;
return `
@@ -91,18 +59,22 @@ export class PuzzleMenuState extends TextualGameState {
getMainContentHTML() {
let html = `
-
-
- ${categories
+
+
+ ${Object.keys(navigation)
.map(
- category => `
-
- `
+ rootCategory =>
+ ``
)
.join("")}
+
+
+
+
+
`;
@@ -154,6 +126,49 @@ export class PuzzleMenuState extends TextualGameState {
.then(() => (this.loading = false));
}
+ /**
+ * Selects a root category
+ * @param {string} rootCategory
+ * @param {string=} category
+ */
+ selectRootCategory(rootCategory, category) {
+ const subCategory = category || navigation[rootCategory][0];
+ console.warn("Select root category", rootCategory, category, "->", subCategory);
+
+ if (this.loading) {
+ return;
+ }
+ if (this.activeCategory === subCategory) {
+ return;
+ }
+
+ const activeCategory = this.htmlElement.querySelector(".active[data-root-category]");
+ if (activeCategory) {
+ activeCategory.classList.remove("active");
+ }
+
+ this.htmlElement.querySelector(`[data-root-category="${rootCategory}"]`).classList.add("active");
+
+ // Rerender buttons
+
+ const subContainer = this.htmlElement.querySelector(".subCategories");
+ while (subContainer.firstChild) {
+ subContainer.removeChild(subContainer.firstChild);
+ }
+
+ 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);
+ }
+
+ this.selectCategory(subCategory);
+ }
+
/**
*
* @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
@@ -167,7 +182,10 @@ export class PuzzleMenuState extends TextualGameState {
for (const puzzle of puzzles) {
const elem = document.createElement("div");
elem.classList.add("puzzle");
- elem.classList.toggle("completed", puzzle.completed);
+
+ if (this.activeCategory !== "mine") {
+ elem.classList.toggle("completed", puzzle.completed);
+ }
if (puzzle.title) {
const title = document.createElement("div");
@@ -176,7 +194,7 @@ export class PuzzleMenuState extends TextualGameState {
elem.appendChild(title);
}
- if (puzzle.author) {
+ if (puzzle.author && !["official", "mine"].includes(this.activeCategory)) {
const author = document.createElement("div");
author.classList.add("author");
author.innerText = "by " + puzzle.author;
@@ -187,7 +205,10 @@ export class PuzzleMenuState extends TextualGameState {
stats.classList.add("stats");
elem.appendChild(stats);
- if (puzzle.downloads > 0) {
+ if (
+ puzzle.downloads > 0 &&
+ !["official", "easy", "medium", "hard"].includes(this.activeCategory)
+ ) {
const difficulty = document.createElement("div");
difficulty.classList.add("difficulty");
@@ -198,14 +219,15 @@ export class PuzzleMenuState extends TextualGameState {
difficulty.innerText = completionPercentage + "%";
stats.appendChild(difficulty);
- if (completionPercentage < 10) {
+ if (completionPercentage < 40) {
difficulty.classList.add("stage--hard");
- } else if (completionPercentage < 30) {
+ difficulty.innerText = T.puzzleMenu.difficulties.hard;
+ } else if (completionPercentage < 80) {
difficulty.classList.add("stage--medium");
- } else if (completionPercentage < 60) {
- difficulty.classList.add("stage--normal");
+ difficulty.innerText = T.puzzleMenu.difficulties.medium;
} else {
difficulty.classList.add("stage--easy");
+ difficulty.innerText = T.puzzleMenu.difficulties.easy;
}
}
@@ -249,10 +271,6 @@ export class PuzzleMenuState extends TextualGameState {
* @returns {Promise}
*/
getPuzzlesForCategory(category) {
- if (category === "levels") {
- return Promise.resolve(BUILTIN_PUZZLES);
- }
-
const result = this.app.clientApi.apiListPuzzles(category);
return result.catch(err => {
logger.error("Failed to get", category, ":", err);
@@ -300,24 +318,28 @@ export class PuzzleMenuState extends TextualGameState {
}
onEnter(payload) {
- this.selectCategory(lastCategory);
+ // Find old category
+ let rootCategory = "categories";
+ for (const [id, children] of Object.entries(navigation)) {
+ if (children.includes(lastCategory)) {
+ rootCategory = id;
+ break;
+ }
+ }
+
+ this.selectRootCategory(rootCategory, lastCategory);
if (payload && payload.error) {
this.dialogs.showWarning(payload.error.title, payload.error.desc);
}
- for (const category of categories) {
- const button = this.htmlElement.querySelector(`[data-category="${category}"]`);
- this.trackClicks(button, () => this.selectCategory(category));
+ for (const rootCategory of Object.keys(navigation)) {
+ const button = this.htmlElement.querySelector(`[data-root-category="${rootCategory}"]`);
+ this.trackClicks(button, () => this.selectRootCategory(rootCategory));
}
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
-
- if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
- // this.createNewPuzzle();
- this.playPuzzle(SAMPLE_PUZZLE);
- }
}
createEmptySavegame() {
diff --git a/translations/base-en.yaml b/translations/base-en.yaml
index a400ca74..791be47e 100644
--- a/translations/base-en.yaml
+++ b/translations/base-en.yaml
@@ -140,11 +140,22 @@ puzzleMenu:
levels: Levels
new: New
top-rated: Top Rated
- mine: My Puzzles
+ mine: Created
short: Short
easy: Easy
+ medium: Medium
hard: Hard
completed: Completed
+ official: Official
+ trending: Trending today
+ categories: Categories
+ difficulties: By Difficulty
+ account: My Puzzles
+
+ difficulties:
+ easy: Easy
+ medium: Medium
+ hard: Hard
validation:
title: Invalid Puzzle