info overlay + script to update images in games and logs

This commit is contained in:
Zutatensuppe 2021-07-11 16:37:34 +02:00
parent 0cb1cec210
commit 518092d269
14 changed files with 148 additions and 93 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>🧩 jigsaw.hyottoko.club</title> <title>🧩 jigsaw.hyottoko.club</title>
<script type="module" crossorigin src="/assets/index.19dfb063.js"></script> <script type="module" crossorigin src="/assets/index.93936dee.js"></script>
<link rel="modulepreload" href="/assets/vendor.684f7bc8.js"> <link rel="modulepreload" href="/assets/vendor.684f7bc8.js">
<link rel="stylesheet" href="/assets/index.22dc307c.css"> <link rel="stylesheet" href="/assets/index.22dc307c.css">
</head> </head>

View file

@ -626,10 +626,12 @@ function getPieceCount(gameId) {
return GAMES[gameId].puzzle.tiles.length; return GAMES[gameId].puzzle.tiles.length;
} }
function getImageUrl(gameId) { function getImageUrl(gameId) {
return GAMES[gameId].puzzle.info.imageUrl; const imageUrl = GAMES[gameId].puzzle.info.image?.url
|| GAMES[gameId].puzzle.info.imageUrl;
if (!imageUrl) {
throw new Error('[2021-07-11] no image url set');
} }
function setImageUrl(gameId, imageUrl) { return imageUrl;
GAMES[gameId].puzzle.info.imageUrl = imageUrl;
} }
function getScoreMode(gameId) { function getScoreMode(gameId) {
return GAMES[gameId].scoreMode; return GAMES[gameId].scoreMode;
@ -1243,7 +1245,6 @@ var GameCommon = {
getFinishedPiecesCount, getFinishedPiecesCount,
getPieceCount, getPieceCount,
getImageUrl, getImageUrl,
setImageUrl,
get: get$1, get: get$1,
getAllGames, getAllGames,
getPlayerBgColor, getPlayerBgColor,
@ -1358,6 +1359,8 @@ var GameLog = {
exists, exists,
log: _log, log: _log,
get, get,
filename,
idxname,
}; };
const log$4 = logger('Images.ts'); const log$4 = logger('Images.ts');
@ -1433,7 +1436,6 @@ const imageFromDb = (db, imageId) => {
return { return {
id: i.id, id: i.id,
filename: i.filename, filename: i.filename,
file: `${UPLOAD_DIR}/${i.filename}`,
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
title: i.title, title: i.title,
tags: getTags(db, i.id), tags: getTags(db, i.id),
@ -1472,7 +1474,6 @@ inner join images i on i.id = ixc.image_id ${where.sql};
return images.map(i => ({ return images.map(i => ({
id: i.id, id: i.id,
filename: i.filename, filename: i.filename,
file: `${UPLOAD_DIR}/${i.filename}`,
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
title: i.title, title: i.title,
tags: getTags(db, i.id), tags: getTags(db, i.id),
@ -1490,7 +1491,6 @@ const allImagesFromDisk = (tags, sort) => {
.map(f => ({ .map(f => ({
id: 0, id: 0,
filename: f, filename: f,
file: `${UPLOAD_DIR}/${f}`,
url: `${UPLOAD_URL}/${encodeURIComponent(f)}`, url: `${UPLOAD_URL}/${encodeURIComponent(f)}`,
title: f.replace(/\.[a-z]+$/, ''), title: f.replace(/\.[a-z]+$/, ''),
tags: [], tags: [],
@ -1501,12 +1501,12 @@ const allImagesFromDisk = (tags, sort) => {
switch (sort) { switch (sort) {
case 'alpha_asc': case 'alpha_asc':
images = images.sort((a, b) => { images = images.sort((a, b) => {
return a.file > b.file ? 1 : -1; return a.filename > b.filename ? 1 : -1;
}); });
break; break;
case 'alpha_desc': case 'alpha_desc':
images = images.sort((a, b) => { images = images.sort((a, b) => {
return a.file < b.file ? 1 : -1; return a.filename < b.filename ? 1 : -1;
}); });
break; break;
case 'date_asc': case 'date_asc':
@ -1552,7 +1552,7 @@ var Images = {
// final resized version of the puzzle image // final resized version of the puzzle image
const TILE_SIZE = 64; const TILE_SIZE = 64;
async function createPuzzle(rng, targetTiles, image, ts, shapeMode) { async function createPuzzle(rng, targetTiles, image, ts, shapeMode) {
const imagePath = image.file; const imagePath = `${UPLOAD_DIR}/${image.filename}`;
const imageUrl = image.url; const imageUrl = image.url;
// determine puzzle information from the image dimensions // determine puzzle information from the image dimensions
const dim = await Images.getDimensions(imagePath); const dim = await Images.getDimensions(imagePath);
@ -1651,6 +1651,7 @@ async function createPuzzle(rng, targetTiles, image, ts, shapeMode) {
// information that was used to create the puzzle // information that was used to create the puzzle
targetTiles: targetTiles, targetTiles: targetTiles,
imageUrl, imageUrl,
image: image,
width: info.width, width: info.width,
height: info.height, height: info.height,
tileSize: info.tileSize, tileSize: info.tileSize,
@ -2114,7 +2115,8 @@ app.get('/api/replay-data', async (req, res) => {
let game = null; let game = null;
if (offset === 0) { if (offset === 0) {
// also need the game // also need the game
game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5], log[0][6], log[0][7]); game = await Game.createGameObject(gameId, log[0][2], log[0][3], // must be ImageInfo
log[0][4], log[0][5], log[0][6], log[0][7]);
} }
res.send({ log, game: game ? Util.encodeGame(game) : null }); res.send({ log, game: game ? Util.encodeGame(game) : null });
}); });

View file

@ -0,0 +1,90 @@
import GameCommon from '../src/common/GameCommon'
import GameLog from '../src/server/GameLog'
import { Game } from '../src/common/Types'
import { logger } from '../src/common/Util'
import { DB_FILE, DB_PATCHES_DIR, UPLOAD_DIR } from '../src/server/Dirs'
import Db from '../src/server/Db'
import GameStorage from '../src/server/GameStorage'
import fs from 'fs'
const log = logger('fix_games_image_info.ts')
import Images from '../src/server/Images'
console.log(DB_FILE)
const db = new Db(DB_FILE, DB_PATCHES_DIR)
db.patch(true)
// ;(async () => {
// let images = db.getMany('images')
// for (let image of images) {
// console.log(image.filename)
// let dim = await Images.getDimensions(`${UPLOAD_DIR}/${image.filename}`)
// console.log(await Images.getDimensions(`${UPLOAD_DIR}/${image.filename}`))
// image.width = dim.w
// image.height = dim.h
// db.upsert('images', image, { id: image.id })
// }
// })()
function fixOne(gameId: string) {
let g = GameCommon.get(gameId)
if (!g) {
return
}
if (!g.puzzle.info.image && g.puzzle.info.imageUrl) {
log.log('game id: ', gameId)
const parts = g.puzzle.info.imageUrl.split('/')
const fileName = parts[parts.length - 1]
const imageRow = db.get('images', {filename: fileName})
if (!imageRow) {
return
}
g.puzzle.info.image = Images.imageFromDb(db, imageRow.id)
log.log(g.puzzle.info.image.title, imageRow.id)
GameStorage.persistGame(gameId)
} else if (g.puzzle.info.image?.id) {
const imageId = g.puzzle.info.image.id
g.puzzle.info.image = Images.imageFromDb(db, imageId)
log.log(g.puzzle.info.image.title, imageId)
GameStorage.persistGame(gameId)
}
// fix log
const file = GameLog.filename(gameId, 0)
if (!fs.existsSync(file)) {
return
}
const lines = fs.readFileSync(file, 'utf-8').split("\n")
const l = lines.filter(line => !!line).map(line => {
return JSON.parse(`[${line}]`)
})
if (l && l[0] && !l[0][3].id) {
log.log(l[0][3])
l[0][3] = g.puzzle.info.image
const newlines = l.map(ll => {
return JSON.stringify(ll).slice(1, -1)
}).join("\n") + "\n"
console.log(g.puzzle.info.image)
// process.exit(0)
fs.writeFileSync(file, newlines)
}
}
function fix() {
GameStorage.loadGames()
GameCommon.getAllGames().forEach((game: Game) => {
fixOne(game.id)
})
}
fix()

View file

@ -1,23 +0,0 @@
import GameCommon from '../src/common/GameCommon'
import { logger } from '../src/common/Util'
import GameStorage from '../src/server/GameStorage'
const log = logger('fix_image.js')
function fix(gameId) {
GameStorage.loadGame(gameId)
let changed = false
let imgUrl = GameCommon.getImageUrl(gameId)
if (imgUrl.match(/^\/example-images\//)) {
log.log(`found bad imgUrl: ${imgUrl}`)
imgUrl = imgUrl.replace(/^\/example-images\//, '/uploads/')
GameCommon.setImageUrl(gameId, imgUrl)
changed = true
}
if (changed) {
GameStorage.persistGame(gameId)
}
}
fix(process.argv[2])

View file

@ -162,11 +162,12 @@ function getPieceCount(gameId: string): number {
} }
function getImageUrl(gameId: string): string { function getImageUrl(gameId: string): string {
return GAMES[gameId].puzzle.info.imageUrl const imageUrl = GAMES[gameId].puzzle.info.image?.url
|| GAMES[gameId].puzzle.info.imageUrl
if (!imageUrl) {
throw new Error('[2021-07-11] no image url set')
} }
return imageUrl
function setImageUrl(gameId: string, imageUrl: string): void {
GAMES[gameId].puzzle.info.imageUrl = imageUrl
} }
function getScoreMode(gameId: string): ScoreMode { function getScoreMode(gameId: string): ScoreMode {
@ -895,7 +896,6 @@ export default {
getFinishedPiecesCount, getFinishedPiecesCount,
getPieceCount, getPieceCount,
getImageUrl, getImageUrl,
setImageUrl,
get, get,
getAllGames, getAllGames,
getPlayerBgColor, getPlayerBgColor,

View file

@ -93,7 +93,7 @@ export interface Image {
export interface GameSettings { export interface GameSettings {
tiles: number tiles: number
image: Image image: ImageInfo
scoreMode: ScoreMode scoreMode: ScoreMode
shapeMode: ShapeMode shapeMode: ShapeMode
snapMode: SnapMode snapMode: SnapMode
@ -152,11 +152,23 @@ export interface PieceChange {
group?: number group?: number
} }
export interface ImageInfo
{
id: number
filename: string
url: string
title: string
tags: Tag[]
created: Timestamp
width: number
height: number
}
export interface PuzzleInfo { export interface PuzzleInfo {
table: PuzzleTable table: PuzzleTable
targetTiles: number targetTiles: number
imageUrl: string imageUrl?: string // deprecated, use image.url instead
imageTitle: string image?: ImageInfo
width: number width: number
height: number height: number

View file

@ -6,19 +6,19 @@
</tr> </tr>
<tr> <tr>
<td>Image Title: </td> <td>Image Title: </td>
<td>{{game.puzzle.info.imageTitle}}</td> <td>{{game.puzzle.info.image.title}}</td>
</tr> </tr>
<tr> <tr>
<td>Snap Mode: </td> <td>Snap Mode: </td>
<td>{{scoreMode[0]}}</td> <td><span :title="snapMode[1]">{{scoreMode[0]}}</span></td>
</tr> </tr>
<tr> <tr>
<td>Shape Mode: </td> <td>Shape Mode: </td>
<td>{{shapeMode[0]}}</td> <td><span :title="snapMode[1]">{{shapeMode[0]}}</span></td>
</tr> </tr>
<tr> <tr>
<td>Score Mode: </td> <td>Score Mode: </td>
<td>{{snapMode[0]}}</td> <td><span :title="snapMode[1]">{{snapMode[0]}}</span></td>
</tr> </tr>
</table> </table>
</div> </div>

View file

@ -1,9 +1,9 @@
import GameCommon from './../common/GameCommon' import GameCommon from './../common/GameCommon'
import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode, Timestamp } from './../common/Types' import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode,ImageInfo, Timestamp } from './../common/Types'
import Util, { logger } from './../common/Util' import Util, { logger } from './../common/Util'
import { Rng } from './../common/Rng' import { Rng } from './../common/Rng'
import GameLog from './GameLog' import GameLog from './GameLog'
import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle' import { createPuzzle } from './Puzzle'
import Protocol from './../common/Protocol' import Protocol from './../common/Protocol'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
@ -12,7 +12,7 @@ const log = logger('Game.ts')
async function createGameObject( async function createGameObject(
gameId: string, gameId: string,
targetTiles: number, targetTiles: number,
image: PuzzleCreationImageInfo, image: ImageInfo,
ts: Timestamp, ts: Timestamp,
scoreMode: ScoreMode, scoreMode: ScoreMode,
shapeMode: ShapeMode, shapeMode: ShapeMode,
@ -35,7 +35,7 @@ async function createGameObject(
async function createGame( async function createGame(
gameId: string, gameId: string,
targetTiles: number, targetTiles: number,
image: PuzzleCreationImageInfo, image: ImageInfo,
ts: Timestamp, ts: Timestamp,
scoreMode: ScoreMode, scoreMode: ScoreMode,
shapeMode: ShapeMode, shapeMode: ShapeMode,

View file

@ -100,4 +100,6 @@ export default {
exists, exists,
log: _log, log: _log,
get, get,
filename,
idxname,
} }

View file

@ -7,30 +7,10 @@ import {UPLOAD_DIR, UPLOAD_URL} from './Dirs'
import Db, { OrderBy, WhereRaw } from './Db' import Db, { OrderBy, WhereRaw } from './Db'
import { Dim } from '../common/Geometry' import { Dim } from '../common/Geometry'
import { logger } from '../common/Util' import { logger } from '../common/Util'
import { Timestamp } from '../common/Types' import { Tag, ImageInfo } from '../common/Types'
const log = logger('Images.ts') const log = logger('Images.ts')
interface Tag
{
id: number
slug: string
title: string
}
interface ImageInfo
{
id: number
filename: string
file: string
url: string
title: string
tags: Tag[]
created: Timestamp
width: number
height: number
}
const resizeImage = async (filename: string): Promise<void> => { const resizeImage = async (filename: string): Promise<void> => {
if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) { if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) {
return return
@ -106,7 +86,6 @@ const imageFromDb = (db: Db, imageId: number): ImageInfo => {
return { return {
id: i.id, id: i.id,
filename: i.filename, filename: i.filename,
file: `${UPLOAD_DIR}/${i.filename}`,
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
title: i.title, title: i.title,
tags: getTags(db, i.id), tags: getTags(db, i.id),
@ -152,7 +131,6 @@ inner join images i on i.id = ixc.image_id ${where.sql};
return images.map(i => ({ return images.map(i => ({
id: i.id as number, id: i.id as number,
filename: i.filename, filename: i.filename,
file: `${UPLOAD_DIR}/${i.filename}`,
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
title: i.title, title: i.title,
tags: getTags(db, i.id), tags: getTags(db, i.id),
@ -174,7 +152,6 @@ const allImagesFromDisk = (
.map(f => ({ .map(f => ({
id: 0, id: 0,
filename: f, filename: f,
file: `${UPLOAD_DIR}/${f}`,
url: `${UPLOAD_URL}/${encodeURIComponent(f)}`, url: `${UPLOAD_URL}/${encodeURIComponent(f)}`,
title: f.replace(/\.[a-z]+$/, ''), title: f.replace(/\.[a-z]+$/, ''),
tags: [] as Tag[], tags: [] as Tag[],
@ -186,13 +163,13 @@ const allImagesFromDisk = (
switch (sort) { switch (sort) {
case 'alpha_asc': case 'alpha_asc':
images = images.sort((a, b) => { images = images.sort((a, b) => {
return a.file > b.file ? 1 : -1 return a.filename > b.filename ? 1 : -1
}) })
break; break;
case 'alpha_desc': case 'alpha_desc':
images = images.sort((a, b) => { images = images.sort((a, b) => {
return a.file < b.file ? 1 : -1 return a.filename < b.filename ? 1 : -1
}) })
break; break;

View file

@ -1,14 +1,9 @@
import Util from './../common/Util' import Util from './../common/Util'
import { Rng } from './../common/Rng' import { Rng } from './../common/Rng'
import Images from './Images' import Images from './Images'
import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle, ShapeMode } from '../common/Types' import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle, ShapeMode, ImageInfo } from '../common/Types'
import { Dim, Point } from '../common/Geometry' import { Dim, Point } from '../common/Geometry'
import { UPLOAD_DIR } from './Dirs'
export interface PuzzleCreationImageInfo {
file: string
url: string
title: string
}
export interface PuzzleCreationInfo { export interface PuzzleCreationInfo {
width: number width: number
@ -28,11 +23,11 @@ const TILE_SIZE = 64
async function createPuzzle( async function createPuzzle(
rng: Rng, rng: Rng,
targetTiles: number, targetTiles: number,
image: PuzzleCreationImageInfo, image: ImageInfo,
ts: number, ts: number,
shapeMode: ShapeMode shapeMode: ShapeMode
): Promise<Puzzle> { ): Promise<Puzzle> {
const imagePath = image.file const imagePath = `${UPLOAD_DIR}/${image.filename}`
const imageUrl = image.url const imageUrl = image.url
// determine puzzle information from the image dimensions // determine puzzle information from the image dimensions
@ -140,8 +135,8 @@ async function createPuzzle(
}, },
// information that was used to create the puzzle // information that was used to create the puzzle
targetTiles: targetTiles, targetTiles: targetTiles,
imageUrl, imageUrl, // todo: remove
imageTitle: image.title || '', image: image,
width: info.width, // actual puzzle width (same as bitmap.width) width: info.width, // actual puzzle width (same as bitmap.width)
height: info.height, // actual puzzle height (same as bitmap.height) height: info.height, // actual puzzle height (same as bitmap.height)

View file

@ -19,7 +19,7 @@ import {
UPLOAD_DIR, UPLOAD_DIR,
} from './Dirs' } from './Dirs'
import GameCommon from '../common/GameCommon' import GameCommon from '../common/GameCommon'
import { ServerEvent, Game as GameType, GameSettings, ScoreMode, ShapeMode, SnapMode } from '../common/Types' import { ServerEvent, Game as GameType, GameSettings } from '../common/Types'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
import Db from './Db' import Db from './Db'
@ -87,7 +87,7 @@ app.get('/api/replay-data', async (req, res): Promise<void> => {
game = await Game.createGameObject( game = await Game.createGameObject(
gameId, gameId,
log[0][2], log[0][2],
log[0][3], log[0][3], // must be ImageInfo
log[0][4], log[0][4],
log[0][5], log[0][5],
log[0][6], log[0][6],