Fix rectangular nonogram share generation and update gitignore

This commit is contained in:
2026-02-13 22:09:46 +01:00
parent df336eeb8a
commit c39c22691e
3 changed files with 74 additions and 42 deletions

6
.gitignore vendored
View File

@@ -14,9 +14,3 @@ dist-ssr/
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Security keys and certificates
*.keystore
*.jks
*.p12
*.mobileprovision

View File

@@ -46,6 +46,8 @@ const triggerVibration = () => {
const getShareData = () => ({ const getShareData = () => ({
grid: store.playerGrid, grid: store.playerGrid,
size: store.size, size: store.size,
rows: store.playerGrid?.length || store.size,
cols: store.playerGrid?.[0]?.length || store.size,
currentDensity: store.currentDensity, currentDensity: store.currentDensity,
guideUsageCount: store.guideUsageCount, guideUsageCount: store.guideUsageCount,
hasUsedBoost: store.hasUsedBoost, hasUsedBoost: store.hasUsedBoost,
@@ -71,9 +73,11 @@ const shareTo = async (target) => {
try { try {
// Try native share first if available (supports images) // Try native share first if available (supports images)
if (navigator.share && navigator.canShare) { if (navigator.share && navigator.canShare) {
const blob = await createShareBlob(getShareData(), t, formattedTime.value); const data = getShareData();
const blob = await createShareBlob(data, t, formattedTime.value);
if (blob) { if (blob) {
const file = new File([blob], `nonogram-${store.size}x${store.size}.png`, { type: 'image/png' }); const dims = (data.cols && data.rows) ? `${data.cols}x${data.rows}` : `${store.size}x${store.size}`;
const file = new File([blob], `nonogram-${dims}.png`, { type: 'image/png' });
if (navigator.canShare({ files: [file] })) { if (navigator.canShare({ files: [file] })) {
await navigator.share({ await navigator.share({
files: [file], files: [file],

View File

@@ -1,19 +1,30 @@
import { calculateDifficulty } from '@/utils/puzzleUtils'; import { calculateDifficulty } from '@/utils/puzzleUtils';
export function buildShareCanvas(data, t, formattedTime) { export function buildShareCanvas(data, t, formattedTime) {
const { grid, size, currentDensity, guideUsageCount, hasUsedBoost } = data; const { grid, size, rows, cols, currentDensity, guideUsageCount, hasUsedBoost } = data;
if (!grid || !grid.length) return null; if (!grid || !grid.length) return null;
// Backward compatibility if rows/cols not provided
const numRows = rows || size;
const numCols = cols || size;
const appUrl = typeof __APP_HOMEPAGE__ !== 'undefined' ? __APP_HOMEPAGE__ : ''; const appUrl = typeof __APP_HOMEPAGE__ !== 'undefined' ? __APP_HOMEPAGE__ : '';
const maxBoard = 640; const maxBoard = 640;
const cellSize = Math.max(8, Math.floor(maxBoard / size)); // Calculate cell size based on the largest dimension to fit within maxBoard
const boardSize = cellSize * size; const maxDim = Math.max(numRows, numCols);
const cellSize = Math.max(8, Math.floor(maxBoard / maxDim));
const boardWidth = cellSize * numCols;
const boardHeight = cellSize * numRows;
const padding = 28; const padding = 28;
const headerHeight = 64; const headerHeight = 64;
const footerHeight = 28; const footerHeight = 28;
const infoHeight = (guideUsageCount > 0 && hasUsedBoost) ? 65 : 40; const infoHeight = (guideUsageCount > 0 && hasUsedBoost) ? 65 : 40;
const width = boardSize + padding * 2;
const height = boardSize + padding * 2 + headerHeight + footerHeight + infoHeight; const width = boardWidth + padding * 2;
const height = boardHeight + padding * 2 + headerHeight + footerHeight + infoHeight;
const scale = window.devicePixelRatio || 1; const scale = window.devicePixelRatio || 1;
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = width * scale; canvas.width = width * scale;
@@ -38,7 +49,8 @@ export function buildShareCanvas(data, t, formattedTime) {
// Difficulty & Density Info // Difficulty & Density Info
const densityPercent = Math.round(currentDensity * 100); const densityPercent = Math.round(currentDensity * 100);
const { level: difficultyKey } = calculateDifficulty(currentDensity, size); // Calculate difficulty using the max dimension (size) as it relates to complexity
const { level: difficultyKey } = calculateDifficulty(currentDensity, maxDim);
let diffColor = '#33ff33'; let diffColor = '#33ff33';
if (difficultyKey === 'extreme') diffColor = '#ff3333'; if (difficultyKey === 'extreme') diffColor = '#ff3333';
else if (difficultyKey === 'hardest') diffColor = '#ff9933'; else if (difficultyKey === 'hardest') diffColor = '#ff9933';
@@ -56,26 +68,34 @@ export function buildShareCanvas(data, t, formattedTime) {
const gridX = padding; const gridX = padding;
const gridY = padding + headerHeight; const gridY = padding + headerHeight;
ctx.fillStyle = 'rgba(255, 255, 255, 0.06)'; ctx.fillStyle = 'rgba(255, 255, 255, 0.06)';
ctx.fillRect(gridX, gridY, boardSize, boardSize); ctx.fillRect(gridX, gridY, boardWidth, boardHeight);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.12)'; ctx.strokeStyle = 'rgba(255, 255, 255, 0.12)';
ctx.lineWidth = 1; ctx.lineWidth = 1;
for (let i = 0; i <= size; i++) {
// Vertical lines
for (let i = 0; i <= numCols; i++) {
const x = gridX + i * cellSize; const x = gridX + i * cellSize;
const y = gridY + i * cellSize;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x, gridY); ctx.moveTo(x, gridY);
ctx.lineTo(x, gridY + boardSize); ctx.lineTo(x, gridY + boardHeight);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(gridX, y);
ctx.lineTo(gridX + boardSize, y);
ctx.stroke(); ctx.stroke();
} }
// Horizontal lines
for (let i = 0; i <= numRows; i++) {
const y = gridY + i * cellSize;
ctx.beginPath();
ctx.moveTo(gridX, y);
ctx.lineTo(gridX + boardWidth, y);
ctx.stroke();
}
ctx.fillStyle = '#00f2fe'; ctx.fillStyle = '#00f2fe';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = Math.max(1.5, Math.floor(cellSize * 0.12)); ctx.lineWidth = Math.max(1.5, Math.floor(cellSize * 0.12));
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) { for (let r = 0; r < numRows; r++) {
for (let c = 0; c < numCols; c++) {
const state = grid[r]?.[c]; const state = grid[r]?.[c];
if (state === 1) { if (state === 1) {
const x = gridX + c * cellSize + 1; const x = gridX + c * cellSize + 1;
@@ -107,7 +127,7 @@ export function buildShareCanvas(data, t, formattedTime) {
ctx.fillStyle = '#ff4d4d'; ctx.fillStyle = '#ff4d4d';
ctx.font = '600 14px "Segoe UI", sans-serif'; ctx.font = '600 14px "Segoe UI", sans-serif';
const totalCells = size * size; const totalCells = numRows * numCols;
const percent = Math.min(100, Math.round((guideUsageCount / totalCells) * 100)); const percent = Math.min(100, Math.round((guideUsageCount / totalCells) * 100));
const guideText = t('win.usedGuide', { count: guideUsageCount, percent }); const guideText = t('win.usedGuide', { count: guideUsageCount, percent });
@@ -129,19 +149,27 @@ export function buildShareCanvas(data, t, formattedTime) {
} }
export function buildShareSVG(data, t, formattedTime) { export function buildShareSVG(data, t, formattedTime) {
const { grid, size, currentDensity, guideUsageCount, hasUsedBoost } = data; const { grid, size, rows, cols, currentDensity, guideUsageCount, hasUsedBoost } = data;
if (!grid || !grid.length) return null; if (!grid || !grid.length) return null;
// Backward compatibility
const numRows = rows || size;
const numCols = cols || size;
const appUrl = typeof __APP_HOMEPAGE__ !== 'undefined' ? __APP_HOMEPAGE__ : ''; const appUrl = typeof __APP_HOMEPAGE__ !== 'undefined' ? __APP_HOMEPAGE__ : '';
const maxBoard = 640; const maxBoard = 640;
const cellSize = Math.max(8, Math.floor(maxBoard / size)); const maxDim = Math.max(numRows, numCols);
const boardSize = cellSize * size; const cellSize = Math.max(8, Math.floor(maxBoard / maxDim));
const boardWidth = cellSize * numCols;
const boardHeight = cellSize * numRows;
const padding = 28; const padding = 28;
const headerHeight = 64; const headerHeight = 64;
const footerHeight = 28; const footerHeight = 28;
const infoHeight = (guideUsageCount > 0 && hasUsedBoost) ? 65 : 40; const infoHeight = (guideUsageCount > 0 && hasUsedBoost) ? 65 : 40;
const width = boardSize + padding * 2; const width = boardWidth + padding * 2;
const height = boardSize + padding * 2 + headerHeight + footerHeight + infoHeight; const height = boardHeight + padding * 2 + headerHeight + footerHeight + infoHeight;
// Colors // Colors
const bgGradientStart = '#1b2a4a'; const bgGradientStart = '#1b2a4a';
@@ -156,7 +184,7 @@ export function buildShareSVG(data, t, formattedTime) {
// Difficulty Logic // Difficulty Logic
const densityPercent = Math.round(currentDensity * 100); const densityPercent = Math.round(currentDensity * 100);
const { level: difficultyKey } = calculateDifficulty(currentDensity, size); const { level: difficultyKey } = calculateDifficulty(currentDensity, maxDim);
let diffColor = '#33ff33'; let diffColor = '#33ff33';
if (difficultyKey === 'extreme') diffColor = '#ff3333'; if (difficultyKey === 'extreme') diffColor = '#ff3333';
@@ -194,16 +222,19 @@ export function buildShareSVG(data, t, formattedTime) {
const gridY = padding + headerHeight; const gridY = padding + headerHeight;
// Grid Background // Grid Background
svgContent += `<rect x="${gridX}" y="${gridY}" width="${boardSize}" height="${boardSize}" fill="${gridColor}"/>`; svgContent += `<rect x="${gridX}" y="${gridY}" width="${boardWidth}" height="${boardHeight}" fill="${gridColor}"/>`;
// Grid Lines // Grid Lines
let gridLines = ''; let gridLines = '';
for (let i = 0; i <= size; i++) { // Vertical
for (let i = 0; i <= numCols; i++) {
const pos = i * cellSize; const pos = i * cellSize;
// Vertical gridLines += `<line x1="${gridX + pos}" y1="${gridY}" x2="${gridX + pos}" y2="${gridY + boardHeight}" stroke="${gridLineColor}" stroke-width="1"/>`;
gridLines += `<line x1="${gridX + pos}" y1="${gridY}" x2="${gridX + pos}" y2="${gridY + boardSize}" stroke="${gridLineColor}" stroke-width="1"/>`; }
// Horizontal // Horizontal
gridLines += `<line x1="${gridX}" y1="${gridY + pos}" x2="${gridX + boardSize}" y2="${gridY + pos}" stroke="${gridLineColor}" stroke-width="1"/>`; for (let i = 0; i <= numRows; i++) {
const pos = i * cellSize;
gridLines += `<line x1="${gridX}" y1="${gridY + pos}" x2="${gridX + boardWidth}" y2="${gridY + pos}" stroke="${gridLineColor}" stroke-width="1"/>`;
} }
svgContent += gridLines; svgContent += gridLines;
@@ -211,8 +242,8 @@ export function buildShareSVG(data, t, formattedTime) {
let cells = ''; let cells = '';
const lineWidth = Math.max(1.5, Math.floor(cellSize * 0.12)); const lineWidth = Math.max(1.5, Math.floor(cellSize * 0.12));
for (let r = 0; r < size; r++) { for (let r = 0; r < numRows; r++) {
for (let c = 0; c < size; c++) { for (let c = 0; c < numCols; c++) {
const state = grid[r]?.[c]; const state = grid[r]?.[c];
const cx = gridX + c * cellSize; const cx = gridX + c * cellSize;
const cy = gridY + r * cellSize; const cy = gridY + r * cellSize;
@@ -238,7 +269,7 @@ export function buildShareSVG(data, t, formattedTime) {
} }
if (guideUsageCount > 0) { if (guideUsageCount > 0) {
const totalCells = size * size; const totalCells = numRows * numCols;
const percent = Math.min(100, Math.round((guideUsageCount / totalCells) * 100)); const percent = Math.min(100, Math.round((guideUsageCount / totalCells) * 100));
const guideText = t('win.usedGuide', { count: guideUsageCount, percent }); const guideText = t('win.usedGuide', { count: guideUsageCount, percent });
svgContent += `<text x="${padding}" y="${infoY}" font-family="Segoe UI, sans-serif" font-weight="600" font-size="14" fill="#ff4d4d">⚠️ ${guideText}</text>`; svgContent += `<text x="${padding}" y="${infoY}" font-family="Segoe UI, sans-serif" font-weight="600" font-size="14" fill="#ff4d4d">⚠️ ${guideText}</text>`;
@@ -276,7 +307,9 @@ export const downloadShareSVG = (data, t, formattedTime) => {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = url; link.href = url;
link.download = `nonogram-${data.size}x${data.size}.svg`; // Use cols x rows if available, else size x size
const dims = (data.cols && data.rows) ? `${data.cols}x${data.rows}` : `${data.size}x${data.size}`;
link.download = `nonogram-${dims}.svg`;
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
link.remove(); link.remove();
@@ -289,7 +322,8 @@ export const downloadShareImage = async (data, t, formattedTime) => {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = url; link.href = url;
link.download = `nonogram-${data.size}x${data.size}.png`; const dims = (data.cols && data.rows) ? `${data.cols}x${data.rows}` : `${data.size}x${data.size}`;
link.download = `nonogram-${dims}.png`;
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
link.remove(); link.remove();