Improved save and load system, added description and sort options
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
-6
@@ -1,11 +1,21 @@
|
||||
from .. import db
|
||||
from flask_login import UserMixin
|
||||
|
||||
class GameState(UserMixin, db.Model):
|
||||
class GameState(db.Model):
|
||||
__tablename__ = "game_state"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, nullable=False)
|
||||
|
||||
board = db.Column(db.Text, nullable=False) # JSON String
|
||||
mode = db.Column(db.String(10), nullable=False) # queens / rooks
|
||||
|
||||
created_at = db.Column(db.DateTime, default=db.func.now())
|
||||
board = db.Column(db.Text, nullable=False)
|
||||
mode = db.Column(db.String(10), nullable=False)
|
||||
board_size = db.Column(db.Integer, nullable=False, default=8)
|
||||
save_name = db.Column(db.String(80), nullable=False, default="Unnamed Save")
|
||||
save_note = db.Column(db.Text, nullable=False, default="")
|
||||
is_favorite = db.Column(db.Boolean, nullable=False, default=False)
|
||||
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
|
||||
updated_at = db.Column(
|
||||
db.DateTime,
|
||||
nullable=False,
|
||||
server_default=db.func.now(),
|
||||
onupdate=db.func.now()
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+86
-20
@@ -23,6 +23,10 @@ def parse_board_size(raw_size, default=DEFAULT_BOARD_SIZE):
|
||||
return max(MIN_BOARD_SIZE, min(MAX_BOARD_SIZE, board_size))
|
||||
|
||||
|
||||
def parse_favorite_flag(raw_value):
|
||||
return str(raw_value).lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def solve_queens(board_size):
|
||||
queen_positions = [-1] * board_size
|
||||
queen_solutions = []
|
||||
@@ -67,12 +71,43 @@ def serialize_game_state(game_state):
|
||||
"id": game_state.id,
|
||||
"board": board,
|
||||
"mode": game_state.mode,
|
||||
"board_size": len(board),
|
||||
"board_size": game_state.board_size,
|
||||
"save_name": game_state.save_name,
|
||||
"save_note": game_state.save_note,
|
||||
"is_favorite": game_state.is_favorite,
|
||||
"pieces_placed": sum(1 for column in board if column != -1),
|
||||
"created_at": game_state.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
"created_at": game_state.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"updated_at": game_state.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
}
|
||||
|
||||
|
||||
def build_save_point_query():
|
||||
query = GameState.query.filter_by(user_id=current_user.id)
|
||||
|
||||
selected_mode = request.args.get("mode")
|
||||
board_size = request.args.get("board_size")
|
||||
favorites_only = request.args.get("favorites_only")
|
||||
sort_by = request.args.get("sort", "newest")
|
||||
|
||||
if selected_mode in {"queens", "rooks"}:
|
||||
query = query.filter_by(mode=selected_mode)
|
||||
|
||||
if board_size not in {None, "", "all"}:
|
||||
query = query.filter_by(board_size=parse_board_size(board_size))
|
||||
|
||||
if parse_favorite_flag(favorites_only):
|
||||
query = query.filter_by(is_favorite=True)
|
||||
|
||||
sort_options = {
|
||||
"newest": GameState.created_at.desc(),
|
||||
"oldest": GameState.created_at.asc(),
|
||||
"updated": GameState.updated_at.desc(),
|
||||
"favorites": GameState.is_favorite.desc()
|
||||
}
|
||||
|
||||
return query.order_by(sort_options.get(sort_by, GameState.created_at.desc()), GameState.id.desc())
|
||||
|
||||
|
||||
@main_bp.route("/")
|
||||
def home():
|
||||
return render_template("index.html")
|
||||
@@ -127,29 +162,63 @@ def save_game():
|
||||
|
||||
board = data["board"]
|
||||
piece_mode = data["mode"]
|
||||
board_size = parse_board_size(data.get("boardSize"), default=len(board))
|
||||
save_name = (data.get("saveName") or "").strip() or f"{piece_mode.title()} Save"
|
||||
save_note = (data.get("saveNote") or "").strip()
|
||||
is_favorite = bool(data.get("isFavorite"))
|
||||
|
||||
game = GameState(
|
||||
game_state = GameState(
|
||||
user_id=current_user.id,
|
||||
board=json.dumps(board),
|
||||
mode=piece_mode
|
||||
mode=piece_mode,
|
||||
board_size=board_size,
|
||||
save_name=save_name,
|
||||
save_note=save_note,
|
||||
is_favorite=is_favorite
|
||||
)
|
||||
|
||||
db.session.add(game)
|
||||
db.session.add(game_state)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"status": "saved"})
|
||||
return jsonify({
|
||||
"status": "saved",
|
||||
"save_point": serialize_game_state(game_state)
|
||||
})
|
||||
|
||||
|
||||
@main_bp.route("/save_points")
|
||||
@login_required
|
||||
def list_save_points():
|
||||
saved_games = (
|
||||
GameState.query.filter_by(user_id=current_user.id)
|
||||
.order_by(GameState.id.desc())
|
||||
.all()
|
||||
)
|
||||
save_points = build_save_point_query().all()
|
||||
return jsonify([serialize_game_state(game_state) for game_state in save_points])
|
||||
|
||||
return jsonify([serialize_game_state(game_state) for game_state in saved_games])
|
||||
|
||||
@main_bp.route("/save_points/<int:game_id>/favorite", methods=["POST"])
|
||||
@login_required
|
||||
def toggle_save_point_favorite(game_id):
|
||||
game_state = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
|
||||
|
||||
if not game_state:
|
||||
return jsonify({"error": "Save point not found"}), 404
|
||||
|
||||
game_state.is_favorite = not game_state.is_favorite
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(serialize_game_state(game_state))
|
||||
|
||||
|
||||
@main_bp.route("/save_points/<int:game_id>", methods=["DELETE"])
|
||||
@login_required
|
||||
def delete_save_point(game_id):
|
||||
game_state = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
|
||||
|
||||
if not game_state:
|
||||
return jsonify({"error": "Save point not found"}), 404
|
||||
|
||||
db.session.delete(game_state)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"status": "deleted"})
|
||||
|
||||
|
||||
@main_bp.route("/load")
|
||||
@@ -157,25 +226,22 @@ def list_save_points():
|
||||
def load_game():
|
||||
latest_game = (
|
||||
GameState.query.filter_by(user_id=current_user.id)
|
||||
.order_by(GameState.id.desc())
|
||||
.order_by(GameState.created_at.desc(), GameState.id.desc())
|
||||
.first()
|
||||
)
|
||||
|
||||
if not latest_game:
|
||||
return jsonify({"board": [-1] * DEFAULT_BOARD_SIZE, "mode": "queens"})
|
||||
|
||||
return jsonify({
|
||||
"board": json.loads(latest_game.board),
|
||||
"mode": latest_game.mode
|
||||
})
|
||||
return jsonify(serialize_game_state(latest_game))
|
||||
|
||||
|
||||
@main_bp.route("/load/<int:game_id>")
|
||||
@login_required
|
||||
def load_specific_game(game_id):
|
||||
selected_game = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
|
||||
game_state = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
|
||||
|
||||
if not selected_game:
|
||||
if not game_state:
|
||||
return jsonify({"error": "Save point not found"}), 404
|
||||
|
||||
return jsonify(serialize_game_state(selected_game))
|
||||
return jsonify(serialize_game_state(game_state))
|
||||
|
||||
@@ -190,6 +190,10 @@ body {
|
||||
width: min(380px, 92vw);
|
||||
}
|
||||
|
||||
.save-sidebar {
|
||||
width: min(360px, 90vw);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -212,6 +216,29 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar-input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
font: inherit;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sidebar-textarea {
|
||||
resize: vertical;
|
||||
min-height: 96px;
|
||||
}
|
||||
|
||||
.sidebar-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 16px 0 18px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.sidebar-size-row {
|
||||
margin: 12px 0 18px;
|
||||
font-size: 18px;
|
||||
@@ -228,6 +255,14 @@ body {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.save-point-filters {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.save-point-card {
|
||||
border: 1px solid #dbe4ee;
|
||||
border-radius: 10px;
|
||||
@@ -253,6 +288,32 @@ body {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.save-point-note {
|
||||
margin: 10px 0 0;
|
||||
color: #475569;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.save-point-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.save-point-actions .btn {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn.ghost {
|
||||
background: #e2e8f0;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.btn.ghost:hover {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
|
||||
.sidebar-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
+150
-15
@@ -15,6 +15,8 @@ const btnQueens = document.getElementById("btnQueens");
|
||||
const btnRooks = document.getElementById("btnRooks");
|
||||
const settingsSidebar = document.getElementById("settingsSidebar");
|
||||
const sidebarBackdrop = document.getElementById("sidebarBackdrop");
|
||||
const saveSidebar = document.getElementById("saveSidebar");
|
||||
const saveBackdrop = document.getElementById("saveBackdrop");
|
||||
const savePointsSidebar = document.getElementById("savePointsSidebar");
|
||||
const savePointsBackdrop = document.getElementById("savePointsBackdrop");
|
||||
const boardSizeInput = document.getElementById("boardSizeInput");
|
||||
@@ -23,6 +25,13 @@ const boardSizeValueMirror = document.getElementById("boardSizeValueMirror");
|
||||
const sidebarBoardSizeValue = document.getElementById("sidebarBoardSizeValue");
|
||||
const sidebarBoardSizeValueMirror = document.getElementById("sidebarBoardSizeValueMirror");
|
||||
const savePointsList = document.getElementById("savePointsList");
|
||||
const saveNameInput = document.getElementById("saveNameInput");
|
||||
const saveNoteInput = document.getElementById("saveNoteInput");
|
||||
const saveFavoriteInput = document.getElementById("saveFavoriteInput");
|
||||
const savePointModeFilter = document.getElementById("savePointModeFilter");
|
||||
const savePointSizeFilter = document.getElementById("savePointSizeFilter");
|
||||
const savePointSortFilter = document.getElementById("savePointSortFilter");
|
||||
const savePointFavoritesOnly = document.getElementById("savePointFavoritesOnly");
|
||||
|
||||
function createEmptyBoard(size) {
|
||||
return Array(size).fill(EMPTY_CELL);
|
||||
@@ -67,6 +76,12 @@ function setMode(newMode) {
|
||||
updateModeButtons();
|
||||
}
|
||||
|
||||
function closeAllPanels() {
|
||||
toggleSidebar(false);
|
||||
toggleSavePanel(false);
|
||||
toggleSavePointsPanel(false);
|
||||
}
|
||||
|
||||
function getConflicts() {
|
||||
const conflicts = [];
|
||||
|
||||
@@ -178,6 +193,7 @@ function toggleSidebar(forceOpen) {
|
||||
const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !settingsSidebar.classList.contains("open");
|
||||
|
||||
if (shouldOpen) {
|
||||
toggleSavePanel(false);
|
||||
toggleSavePointsPanel(false);
|
||||
}
|
||||
|
||||
@@ -186,9 +202,27 @@ function toggleSidebar(forceOpen) {
|
||||
settingsSidebar.setAttribute("aria-hidden", String(!shouldOpen));
|
||||
}
|
||||
|
||||
function toggleSavePanel(forceOpen) {
|
||||
const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !saveSidebar.classList.contains("open");
|
||||
|
||||
if (shouldOpen) {
|
||||
toggleSidebar(false);
|
||||
toggleSavePointsPanel(false);
|
||||
}
|
||||
|
||||
saveSidebar.classList.toggle("open", shouldOpen);
|
||||
saveBackdrop.classList.toggle("visible", shouldOpen);
|
||||
saveSidebar.setAttribute("aria-hidden", String(!shouldOpen));
|
||||
}
|
||||
|
||||
function toggleSavePointsPanel(forceOpen) {
|
||||
const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !savePointsSidebar.classList.contains("open");
|
||||
|
||||
if (shouldOpen) {
|
||||
toggleSidebar(false);
|
||||
toggleSavePanel(false);
|
||||
}
|
||||
|
||||
savePointsSidebar.classList.toggle("open", shouldOpen);
|
||||
savePointsBackdrop.classList.toggle("visible", shouldOpen);
|
||||
savePointsSidebar.setAttribute("aria-hidden", String(!shouldOpen));
|
||||
@@ -198,6 +232,21 @@ function getPieceLabel(selectedMode) {
|
||||
return selectedMode === "queens" ? "Dame" : "Turm";
|
||||
}
|
||||
|
||||
function getPlacedPieceCount() {
|
||||
return board.filter((col) => col !== EMPTY_CELL).length;
|
||||
}
|
||||
|
||||
function resetSaveForm() {
|
||||
saveNameInput.value = `${getPieceLabel(mode)} ${boardSize}x${boardSize} - ${getPlacedPieceCount()} gesetzt`;
|
||||
saveNoteInput.value = "";
|
||||
saveFavoriteInput.checked = false;
|
||||
}
|
||||
|
||||
function openSavePanel() {
|
||||
resetSaveForm();
|
||||
toggleSavePanel(true);
|
||||
}
|
||||
|
||||
function getBoardPreview(savedBoard, limit = 4) {
|
||||
const preview = [];
|
||||
|
||||
@@ -216,11 +265,40 @@ function getBoardPreview(savedBoard, limit = 4) {
|
||||
return preview.length > 0 ? preview.join(", ") : "Noch keine Figuren gesetzt";
|
||||
}
|
||||
|
||||
function getSavePointFilters() {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (savePointModeFilter.value !== "all") {
|
||||
params.set("mode", savePointModeFilter.value);
|
||||
}
|
||||
|
||||
if (savePointSizeFilter.value !== "all") {
|
||||
params.set("board_size", savePointSizeFilter.value);
|
||||
}
|
||||
|
||||
params.set("sort", savePointSortFilter.value);
|
||||
|
||||
if (savePointFavoritesOnly.checked) {
|
||||
params.set("favorites_only", "true");
|
||||
}
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
function createSavePointActionButton(label, className, onClick) {
|
||||
const button = document.createElement("button");
|
||||
button.className = className;
|
||||
button.type = "button";
|
||||
button.textContent = label;
|
||||
button.onclick = onClick;
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderSavePoints(savePoints) {
|
||||
savePointsList.innerHTML = "";
|
||||
|
||||
if (savePoints.length === 0) {
|
||||
savePointsList.innerHTML = '<p class="sidebar-hint">Noch keine Speicherstaende gefunden.</p>';
|
||||
savePointsList.innerHTML = '<p class="sidebar-hint">Keine Speicherstaende fuer diese Filter gefunden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,42 +306,54 @@ function renderSavePoints(savePoints) {
|
||||
const item = document.createElement("article");
|
||||
const title = document.createElement("h4");
|
||||
const createdAt = document.createElement("p");
|
||||
const updatedAt = document.createElement("p");
|
||||
const boardMeta = document.createElement("p");
|
||||
const preview = document.createElement("p");
|
||||
const loadButton = document.createElement("button");
|
||||
const note = document.createElement("p");
|
||||
const actions = document.createElement("div");
|
||||
|
||||
item.className = "save-point-card";
|
||||
title.textContent = `Save Point #${savePoint.id}`;
|
||||
title.textContent = `${savePoint.is_favorite ? "★ " : ""}${savePoint.save_name}`;
|
||||
createdAt.className = "save-point-meta";
|
||||
createdAt.textContent = `Gespeichert: ${savePoint.created_at}`;
|
||||
createdAt.textContent = `Erstellt: ${savePoint.created_at}`;
|
||||
updatedAt.className = "save-point-meta";
|
||||
updatedAt.textContent = `Aktualisiert: ${savePoint.updated_at}`;
|
||||
boardMeta.className = "save-point-meta";
|
||||
boardMeta.textContent = `${getPieceLabel(savePoint.mode)} | ${savePoint.board_size} x ${savePoint.board_size} | ${savePoint.pieces_placed}/${savePoint.board_size} gesetzt`;
|
||||
preview.className = "save-point-preview";
|
||||
preview.textContent = `Vorschau: ${getBoardPreview(savePoint.board)}`;
|
||||
note.className = "save-point-note";
|
||||
note.textContent = savePoint.save_note ? `Notiz: ${savePoint.save_note}` : "Notiz: Keine Notiz";
|
||||
actions.className = "save-point-actions";
|
||||
|
||||
loadButton.className = "btn";
|
||||
loadButton.type = "button";
|
||||
loadButton.textContent = "Diesen laden";
|
||||
loadButton.onclick = () => loadSavePoint(savePoint.id);
|
||||
actions.appendChild(createSavePointActionButton("Laden", "btn", () => loadSavePoint(savePoint.id)));
|
||||
actions.appendChild(createSavePointActionButton(
|
||||
savePoint.is_favorite ? "Favorit entfernen" : "Als Favorit",
|
||||
"btn secondary",
|
||||
() => toggleSavePointFavorite(savePoint.id)
|
||||
));
|
||||
actions.appendChild(createSavePointActionButton("Loeschen", "btn ghost", () => deleteSavePoint(savePoint.id)));
|
||||
|
||||
item.appendChild(title);
|
||||
item.appendChild(createdAt);
|
||||
item.appendChild(updatedAt);
|
||||
item.appendChild(boardMeta);
|
||||
item.appendChild(preview);
|
||||
item.appendChild(loadButton);
|
||||
item.appendChild(note);
|
||||
item.appendChild(actions);
|
||||
savePointsList.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshSavePoints() {
|
||||
const response = await fetch("/save_points");
|
||||
const queryString = getSavePointFilters();
|
||||
const response = await fetch(`/save_points${queryString ? `?${queryString}` : ""}`);
|
||||
const savePoints = await response.json();
|
||||
renderSavePoints(savePoints);
|
||||
}
|
||||
|
||||
async function openSavePointsPanel() {
|
||||
await refreshSavePoints();
|
||||
toggleSidebar(false);
|
||||
toggleSavePointsPanel(true);
|
||||
}
|
||||
|
||||
@@ -316,7 +406,7 @@ function updateQueenList() {
|
||||
}
|
||||
|
||||
async function saveGame() {
|
||||
await fetch("/save", {
|
||||
const response = await fetch("/save", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
@@ -324,12 +414,23 @@ async function saveGame() {
|
||||
body: JSON.stringify({
|
||||
board,
|
||||
mode,
|
||||
boardSize
|
||||
boardSize,
|
||||
saveName: saveNameInput.value,
|
||||
saveNote: saveNoteInput.value,
|
||||
isFavorite: saveFavoriteInput.checked
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
setStatus("❌ Der Speicherstand konnte nicht gespeichert werden.", "red");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
toggleSavePanel(false);
|
||||
await refreshSavePoints();
|
||||
alert("💾 Spiel gespeichert!");
|
||||
setStatus(`Speicherpunkt "${data.save_point.save_name}" gespeichert.`, "green");
|
||||
}
|
||||
|
||||
async function loadGame() {
|
||||
@@ -349,10 +450,44 @@ async function loadSavePoint(savePointId) {
|
||||
const data = await response.json();
|
||||
|
||||
applyLoadedGame(data);
|
||||
setStatus(`Speicherstand #${savePointId} geladen.`, "green");
|
||||
setStatus(`Speicherpunkt "${data.save_name}" geladen.`, "green");
|
||||
toggleSavePointsPanel(false);
|
||||
}
|
||||
|
||||
async function toggleSavePointFavorite(savePointId) {
|
||||
const response = await fetch(`/save_points/${savePointId}/favorite`, {
|
||||
method: "POST"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
setStatus("❌ Favorit konnte nicht aktualisiert werden.", "red");
|
||||
return;
|
||||
}
|
||||
|
||||
await refreshSavePoints();
|
||||
setStatus("Favoritenstatus aktualisiert.", "#2563eb");
|
||||
}
|
||||
|
||||
async function deleteSavePoint(savePointId) {
|
||||
const confirmed = window.confirm("Diesen Speicherpunkt wirklich loeschen?");
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`/save_points/${savePointId}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
setStatus("❌ Speicherpunkt konnte nicht geloescht werden.", "red");
|
||||
return;
|
||||
}
|
||||
|
||||
await refreshSavePoints();
|
||||
setStatus("Speicherpunkt geloescht.", "#2563eb");
|
||||
}
|
||||
|
||||
pendingBoardSize = boardSize;
|
||||
updateModeButtons();
|
||||
updateBoardSizeDisplays(boardSize);
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<button class="btn" onclick="checkSolution()">Check</button>
|
||||
<button class="btn secondary" onclick="resetBoard()">Reset</button>
|
||||
|
||||
<button class="btn" onclick="saveGame()">💾 Save</button>
|
||||
<button class="btn" onclick="openSavePanel()">💾 Save</button>
|
||||
<button class="btn" onclick="openSavePointsPanel()">📂 Load</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,6 +95,29 @@
|
||||
<p class="sidebar-hint">Das Brett wird beim Ändern zur Sicherheit zurückgesetzt.</p>
|
||||
</aside>
|
||||
|
||||
<aside class="sidebar save-sidebar" id="saveSidebar" aria-hidden="true">
|
||||
<div class="sidebar-header">
|
||||
<h3>Speicherpunkt</h3>
|
||||
<button class="btn secondary sidebar-close" onclick="toggleSavePanel(false)" type="button">Schliessen</button>
|
||||
</div>
|
||||
|
||||
<p>Gib dem Spielstand einen klaren Namen und optional eine kurze Notiz.</p>
|
||||
|
||||
<label class="sidebar-label" for="saveNameInput">Name</label>
|
||||
<input class="sidebar-input" id="saveNameInput" type="text" maxlength="80" placeholder="z. B. Fast geloeste Dame 8x8">
|
||||
|
||||
<label class="sidebar-label" for="saveNoteInput">Notiz</label>
|
||||
<textarea class="sidebar-input sidebar-textarea" id="saveNoteInput" rows="4" maxlength="240"
|
||||
placeholder="z. B. Gute Startposition fuer Tuermetest"></textarea>
|
||||
|
||||
<label class="sidebar-checkbox">
|
||||
<input id="saveFavoriteInput" type="checkbox">
|
||||
Als Favorit markieren
|
||||
</label>
|
||||
|
||||
<button class="btn" onclick="saveGame()" type="button">Jetzt speichern</button>
|
||||
</aside>
|
||||
|
||||
<aside class="sidebar save-points-sidebar" id="savePointsSidebar" aria-hidden="true">
|
||||
<div class="sidebar-header">
|
||||
<h3>Save Points</h3>
|
||||
@@ -102,12 +125,44 @@
|
||||
</div>
|
||||
|
||||
<p>Waehle einen Speicherstand aus der Liste.</p>
|
||||
|
||||
<div class="save-point-filters">
|
||||
<label class="sidebar-label" for="savePointModeFilter">Modus</label>
|
||||
<select class="sidebar-input" id="savePointModeFilter" onchange="refreshSavePoints()">
|
||||
<option value="all">Alle</option>
|
||||
<option value="queens">Damen</option>
|
||||
<option value="rooks">Tuerme</option>
|
||||
</select>
|
||||
|
||||
<label class="sidebar-label" for="savePointSizeFilter">Brettgroesse</label>
|
||||
<select class="sidebar-input" id="savePointSizeFilter" onchange="refreshSavePoints()">
|
||||
<option value="all">Alle</option>
|
||||
{% for board_size in range(4, 14) %}
|
||||
<option value="{{ board_size }}">{{ board_size }} x {{ board_size }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label class="sidebar-label" for="savePointSortFilter">Sortierung</label>
|
||||
<select class="sidebar-input" id="savePointSortFilter" onchange="refreshSavePoints()">
|
||||
<option value="newest">Neueste zuerst</option>
|
||||
<option value="oldest">Aelteste zuerst</option>
|
||||
<option value="updated">Zuletzt bearbeitet</option>
|
||||
<option value="favorites">Favoriten zuerst</option>
|
||||
</select>
|
||||
|
||||
<label class="sidebar-checkbox">
|
||||
<input id="savePointFavoritesOnly" type="checkbox" onchange="refreshSavePoints()">
|
||||
Nur Favoriten anzeigen
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="save-points-list" id="savePointsList">
|
||||
<p class="sidebar-hint">Noch keine Speicherstaende gefunden.</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="sidebar-backdrop" id="sidebarBackdrop" onclick="toggleSidebar(false)"></div>
|
||||
<div class="sidebar-backdrop" id="saveBackdrop" onclick="toggleSavePanel(false)"></div>
|
||||
<div class="sidebar-backdrop" id="savePointsBackdrop" onclick="toggleSavePointsPanel(false)"></div>
|
||||
|
||||
<script src="../static/js/dashboard.js"></script>
|
||||
|
||||
+6
-1
@@ -11,5 +11,10 @@ CREATE TABLE game_state (
|
||||
user_id INTEGER NOT NULL,
|
||||
board TEXT NOT NULL, -- JSON String (z.B. "[0,4,7,5,2,6,1,3]")
|
||||
mode TEXT NOT NULL, -- "queens" oder "rooks"
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
board_size INTEGER NOT NULL DEFAULT 8,
|
||||
save_name VARCHAR(80) NOT NULL DEFAULT 'Unnamed Save',
|
||||
save_note TEXT NOT NULL DEFAULT '',
|
||||
is_favorite BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
Reference in New Issue
Block a user