1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-06-13 13:04:03 +00:00

- Puzzle DLC browser improvements / new categories

- Fix errors not being thrown sometimes
- Minor other stuff
This commit is contained in:
tobspr 2021-06-19 17:16:01 +02:00
parent 978c07ccc4
commit 416f89bbf4
6 changed files with 159 additions and 112 deletions

View File

@ -15,15 +15,15 @@ $hardwareAcc: null;
// ---------------------------------------- // ----------------------------------------
/** Increased click area for this element, helpful on mobile */ /** Increased click area for this element, helpful on mobile */
@mixin IncreasedClickArea($size) { @mixin IncreasedClickArea($size) {
&::after { // &::after {
content: ""; // content: "";
position: absolute; // position: absolute;
top: #{D(-$size)}; // top: #{D(-$size)};
bottom: #{D(-$size)}; // bottom: #{D(-$size)};
left: #{D(-$size)}; // left: #{D(-$size)};
right: #{D(-$size)}; // right: #{D(-$size)};
// background: rgba(255, 0, 0, 0.3); // // background: rgba(255, 0, 0, 0.3);
} // }
} }
button, button,
.increasedClickArea { .increasedClickArea {

View File

@ -19,13 +19,15 @@
overflow: hidden; overflow: hidden;
> .categoryChooser { > .categoryChooser {
> .categories {
display: grid; display: grid;
grid-auto-columns: 1fr; grid-auto-columns: 1fr;
grid-auto-flow: column; grid-auto-flow: column;
@include S(grid-gap, 2px); @include S(grid-gap, 2px);
@include S(padding-right, 10px); @include S(padding-right, 10px);
@include S(margin-bottom, 5px);
> .category { .category {
background: $accentColorBright; background: $accentColorBright;
border-radius: 0; border-radius: 0;
color: $accentColorDark; color: $accentColorDark;
@ -57,17 +59,27 @@
color: #fff; color: #fff;
} }
} }
&.root {
@include S(padding-top, 10px);
@include S(padding-bottom, 10px);
@include Text;
}
&.child {
@include PlainText;
}
}
} }
} }
> .puzzles { > .puzzles {
display: grid; 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-auto-rows, 65px);
@include S(grid-gap, 7px); @include S(grid-gap, 7px);
@include S(margin-top, 10px); @include S(margin-top, 10px);
@include S(padding-right, 4px); @include S(padding-right, 4px);
@include S(height, 360px); @include S(height, 320px);
overflow-y: scroll; overflow-y: scroll;
pointer-events: all; pointer-events: all;
position: relative; position: relative;
@ -203,14 +215,11 @@
font-weight: bold; font-weight: bold;
@include S(margin-right, 3px); @include S(margin-right, 3px);
opacity: 0.7; opacity: 0.7;
text-transform: uppercase;
&.stage--easy { &.stage--easy {
color: $colorGreenBright; color: $colorGreenBright;
} }
&.stage--normal {
color: #000;
@include DarkThemeInvert;
}
&.stage--medium { &.stage--medium {
color: $colorOrangeBright; color: $colorOrangeBright;
} }

View File

@ -89,8 +89,13 @@ export class StateManager {
const dialogParent = document.createElement("div"); const dialogParent = document.createElement("div");
dialogParent.classList.add("modalDialogParent"); dialogParent.classList.add("modalDialogParent");
document.body.appendChild(dialogParent); document.body.appendChild(dialogParent);
try {
this.currentState.internalEnterCallback(payload); this.currentState.internalEnterCallback(payload);
} catch (ex) {
console.error(ex);
throw ex;
}
this.app.sound.playThemeMusic(this.currentState.getThemeMusic()); this.app.sound.playThemeMusic(this.currentState.getThemeMusic());
this.currentState.onResized(this.app.screenWidth, this.app.screenHeight); this.currentState.onResized(this.app.screenWidth, this.app.screenHeight);

View File

@ -95,7 +95,7 @@ export class MainMenuState extends GameState {
</div> </div>
${ ${
!G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc (!G_WEGAME_VERSION && G_IS_STANDALONE && puzzleDlc) || G_IS_DEV
? ` ? `
<div class="puzzleContainer"> <div class="puzzleContainer">
<img class="dlcLogo" src="${cachebust( <img class="dlcLogo" src="${cachebust(

View File

@ -10,48 +10,15 @@ import { MUSIC } from "../platform/sound";
import { Savegame } from "../savegame/savegame"; import { Savegame } from "../savegame/savegame";
import { T } from "../translations"; import { T } from "../translations";
const categories = ["top-rated", "new", "easy", "short", "hard", "completed", "mine"]; const navigation = {
categories: ["official", "top-rated", "trending", "new"],
/** difficulties: ["easy", "medium", "hard", "short"],
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata} account: ["mine", "completed"],
*/
const SAMPLE_PUZZLE = {
id: 1,
shortKey: "CuCuCuCu",
downloads: 0,
likes: 0,
averageTime: 1,
completions: 1,
difficulty: null,
title: "Level 1",
author: "verylongsteamnamewhichbreaks",
completed: false,
}; };
/**
* @type {import("../savegame/savegame_typedefs").PuzzleMetadata[]}
*/
const BUILTIN_PUZZLES = G_IS_DEV
? [
// { ...SAMPLE_PUZZLE, completed: true },
// { ...SAMPLE_PUZZLE, completed: true },
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
// SAMPLE_PUZZLE,
]
: [];
const logger = createLogger("puzzle-menu"); const logger = createLogger("puzzle-menu");
let lastCategory = categories[0]; let lastCategory = "official";
export class PuzzleMenuState extends TextualGameState { export class PuzzleMenuState extends TextualGameState {
constructor() { constructor() {
@ -79,6 +46,7 @@ export class PuzzleMenuState extends TextualGameState {
<button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button> <button class="styledButton loadPuzzle">${T.puzzleMenu.loadPuzzle}</button>
<button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button> <button class="styledButton createPuzzle">+ ${T.puzzleMenu.createPuzzle}</button>
</div> </div>
</div>`; </div>`;
return ` return `
@ -91,18 +59,22 @@ export class PuzzleMenuState extends TextualGameState {
getMainContentHTML() { getMainContentHTML() {
let html = ` let html = `
<div class="categoryChooser"> <div class="categoryChooser">
${categories
<div class="categories rootCategories">
${Object.keys(navigation)
.map( .map(
category => ` rootCategory =>
<button data-category="${category}" class="styledButton category">${T.puzzleMenu.categories[category]}</button> `<button data-root-category="${rootCategory}" class="styledButton category root">${T.puzzleMenu.categories[rootCategory]}</button>`
`
) )
.join("")} .join("")}
</div> </div>
<div class="categories subCategories">
</div>
</div>
<div class="puzzles" id="mainContainer"></div> <div class="puzzles" id="mainContainer"></div>
`; `;
@ -154,6 +126,49 @@ export class PuzzleMenuState extends TextualGameState {
.then(() => (this.loading = false)); .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 * @param {import("../savegame/savegame_typedefs").PuzzleMetadata[]} puzzles
@ -167,7 +182,10 @@ export class PuzzleMenuState extends TextualGameState {
for (const puzzle of puzzles) { for (const puzzle of puzzles) {
const elem = document.createElement("div"); const elem = document.createElement("div");
elem.classList.add("puzzle"); elem.classList.add("puzzle");
if (this.activeCategory !== "mine") {
elem.classList.toggle("completed", puzzle.completed); elem.classList.toggle("completed", puzzle.completed);
}
if (puzzle.title) { if (puzzle.title) {
const title = document.createElement("div"); const title = document.createElement("div");
@ -176,7 +194,7 @@ export class PuzzleMenuState extends TextualGameState {
elem.appendChild(title); elem.appendChild(title);
} }
if (puzzle.author) { if (puzzle.author && !["official", "mine"].includes(this.activeCategory)) {
const author = document.createElement("div"); const author = document.createElement("div");
author.classList.add("author"); author.classList.add("author");
author.innerText = "by " + puzzle.author; author.innerText = "by " + puzzle.author;
@ -187,7 +205,10 @@ export class PuzzleMenuState extends TextualGameState {
stats.classList.add("stats"); stats.classList.add("stats");
elem.appendChild(stats); elem.appendChild(stats);
if (puzzle.downloads > 0) { if (
puzzle.downloads > 0 &&
!["official", "easy", "medium", "hard"].includes(this.activeCategory)
) {
const difficulty = document.createElement("div"); const difficulty = document.createElement("div");
difficulty.classList.add("difficulty"); difficulty.classList.add("difficulty");
@ -198,14 +219,15 @@ export class PuzzleMenuState extends TextualGameState {
difficulty.innerText = completionPercentage + "%"; difficulty.innerText = completionPercentage + "%";
stats.appendChild(difficulty); stats.appendChild(difficulty);
if (completionPercentage < 10) { if (completionPercentage < 40) {
difficulty.classList.add("stage--hard"); difficulty.classList.add("stage--hard");
} else if (completionPercentage < 30) { difficulty.innerText = T.puzzleMenu.difficulties.hard;
} else if (completionPercentage < 80) {
difficulty.classList.add("stage--medium"); difficulty.classList.add("stage--medium");
} else if (completionPercentage < 60) { difficulty.innerText = T.puzzleMenu.difficulties.medium;
difficulty.classList.add("stage--normal");
} else { } else {
difficulty.classList.add("stage--easy"); difficulty.classList.add("stage--easy");
difficulty.innerText = T.puzzleMenu.difficulties.easy;
} }
} }
@ -249,10 +271,6 @@ export class PuzzleMenuState extends TextualGameState {
* @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>} * @returns {Promise<import("../savegame/savegame_typedefs").PuzzleMetadata[]>}
*/ */
getPuzzlesForCategory(category) { getPuzzlesForCategory(category) {
if (category === "levels") {
return Promise.resolve(BUILTIN_PUZZLES);
}
const result = this.app.clientApi.apiListPuzzles(category); const result = this.app.clientApi.apiListPuzzles(category);
return result.catch(err => { return result.catch(err => {
logger.error("Failed to get", category, ":", err); logger.error("Failed to get", category, ":", err);
@ -300,24 +318,28 @@ export class PuzzleMenuState extends TextualGameState {
} }
onEnter(payload) { 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) { if (payload && payload.error) {
this.dialogs.showWarning(payload.error.title, payload.error.desc); this.dialogs.showWarning(payload.error.title, payload.error.desc);
} }
for (const category of categories) { for (const rootCategory of Object.keys(navigation)) {
const button = this.htmlElement.querySelector(`[data-category="${category}"]`); const button = this.htmlElement.querySelector(`[data-root-category="${rootCategory}"]`);
this.trackClicks(button, () => this.selectCategory(category)); this.trackClicks(button, () => this.selectRootCategory(rootCategory));
} }
this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle()); this.trackClicks(this.htmlElement.querySelector("button.createPuzzle"), () => this.createNewPuzzle());
this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle()); this.trackClicks(this.htmlElement.querySelector("button.loadPuzzle"), () => this.loadPuzzle());
if (G_IS_DEV && globalConfig.debug.testPuzzleMode) {
// this.createNewPuzzle();
this.playPuzzle(SAMPLE_PUZZLE);
}
} }
createEmptySavegame() { createEmptySavegame() {

View File

@ -140,11 +140,22 @@ puzzleMenu:
levels: Levels levels: Levels
new: New new: New
top-rated: Top Rated top-rated: Top Rated
mine: My Puzzles mine: Created
short: Short short: Short
easy: Easy easy: Easy
medium: Medium
hard: Hard hard: Hard
completed: Completed completed: Completed
official: Official
trending: Trending today
categories: Categories
difficulties: By Difficulty
account: My Puzzles
difficulties:
easy: Easy
medium: Medium
hard: Hard
validation: validation:
title: Invalid Puzzle title: Invalid Puzzle