Compare commits
8 Commits
v2026.2.8-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 199bb7e525 | |||
| 5daaf71ae7 | |||
| 0aeec47996 | |||
| 3d67b8f2d9 | |||
| dd9a190fd9 | |||
| f5e5019ea8 | |||
| 3f51eb5db6 | |||
| 55ef7c9301 |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,6 +1,34 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.9-0 (2026-04-23)
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
* Add tracking code for the app #10. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.8-3 (2026-04-22)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* The error message cannot be seen during avatar changing #10. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.8-2 (2026-04-21)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Increase the 2 MB avatar maximum file size to 10 MB #10. [Lang]
|
||||||
|
|
||||||
|
|
||||||
|
## v2026.2.8-1 (2026-04-21)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Upgrade front-end & back-end deps to the latest available version #10. [Lang]
|
||||||
|
|
||||||
|
|
||||||
## v2026.2.8-0 (2026-04-21)
|
## v2026.2.8-0 (2026-04-21)
|
||||||
|
|
||||||
### New
|
### New
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ RUN install-php-extensions \
|
|||||||
sodium
|
sodium
|
||||||
|
|
||||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||||
|
RUN printf '[PHP]\nupload_max_filesize=10M\npost_max_size=11M\n' > "$PHP_INI_DIR/conf.d/uploads.ini"
|
||||||
RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \
|
RUN printf '[opcache]\nopcache.enable=1\nopcache.memory_consumption=256\nopcache.max_accelerated_files=20000\nopcache.validate_timestamps=0\n' \
|
||||||
> "$PHP_INI_DIR/conf.d/opcache.ini"
|
> "$PHP_INI_DIR/conf.d/opcache.ini"
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,21 @@
|
|||||||
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 48px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#profile-avatar-root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar__error {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #e57373;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 120px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-avatar {
|
.profile-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useRef } from 'react';
|
import React, { Fragment, useMemo, useRef } from 'react';
|
||||||
import { string } from 'prop-types';
|
import { string } from 'prop-types';
|
||||||
import { useProfileDataProvider } from '@mine-hooks/useGameDataProvider';
|
import { useProfileDataProvider } from '@mine-hooks/useGameDataProvider';
|
||||||
|
|
||||||
@@ -16,6 +16,8 @@ export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
|
|||||||
const [thumbUrl, setThumbUrl] = React.useState(initialThumbUrl || null);
|
const [thumbUrl, setThumbUrl] = React.useState(initialThumbUrl || null);
|
||||||
const { uploadAvatarMutation: { isPending, error, mutate } } = useProfileDataProvider();
|
const { uploadAvatarMutation: { isPending, error, mutate } } = useProfileDataProvider();
|
||||||
|
|
||||||
|
const errorMessage = useMemo(() => error?.message ?? null, [error]);
|
||||||
|
|
||||||
const handleChange = e => {
|
const handleChange = e => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
@@ -40,9 +42,8 @@ export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorMessage = useMemo(() => error?.message ?? null, [error]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Fragment>
|
||||||
<div
|
<div
|
||||||
className={`profile-avatar${isPending ? ' profile-avatar--loading' : ''}`}
|
className={`profile-avatar${isPending ? ' profile-avatar--loading' : ''}`}
|
||||||
title="Click to change profile picture"
|
title="Click to change profile picture"
|
||||||
@@ -62,10 +63,11 @@ export const AvatarUpload = ({ uploadUrl, initialThumbUrl, initials }) => {
|
|||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
{errorMessage && <div className="profile-avatar__error">{errorMessage}</div>}
|
|
||||||
</div>
|
</div>
|
||||||
|
{errorMessage && <div className="profile-avatar__error">{errorMessage}</div>}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
AvatarUpload.propTypes = {
|
AvatarUpload.propTypes = {
|
||||||
uploadUrl: string.isRequired,
|
uploadUrl: string.isRequired,
|
||||||
|
|||||||
@@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
MineSeeker-specific testing setup and workflows. For general PHPUnit/Symfony testing, see [Symfony Testing Docs](https://symfony.com/doc/current/testing.html).
|
MineSeeker-specific testing setup and workflows. For general PHPUnit/Symfony testing, see [Symfony Testing Docs](https://symfony.com/doc/current/testing.html).
|
||||||
|
|
||||||
|
## Example of the current tests
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ bin/phpunit (master->origin/master|✚1…2⚑1)
|
||||||
|
PHPUnit 13.1.7 by Sebastian Bergmann and contributors.
|
||||||
|
|
||||||
|
Runtime: PHP 8.5.5
|
||||||
|
Configuration: /var/www/splendid/Mine/phpunit.dist.xml
|
||||||
|
|
||||||
|
................................................................. 65 / 71 ( 91%)
|
||||||
|
...... 71 / 71 (100%)
|
||||||
|
|
||||||
|
Time: 00:07.319, Memory: 86.50 MB
|
||||||
|
|
||||||
|
OK (71 tests, 227 assertions)
|
||||||
|
|
||||||
|
Faker seed used: 918823
|
||||||
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use League\Flysystem\FilesystemException;
|
use League\Flysystem\FilesystemException;
|
||||||
use League\Flysystem\FilesystemOperator;
|
use League\Flysystem\FilesystemOperator;
|
||||||
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
|
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
|
||||||
|
use Liip\ImagineBundle\Service\FilterService;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
@@ -140,6 +141,7 @@ class ProfileController extends AbstractController
|
|||||||
Request $request,
|
Request $request,
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
CacheManager $cacheManager,
|
CacheManager $cacheManager,
|
||||||
|
FilterService $filterService,
|
||||||
#[Autowire(service: 'mineseeker.media.storage')] FilesystemOperator $mediaStorage,
|
#[Autowire(service: 'mineseeker.media.storage')] FilesystemOperator $mediaStorage,
|
||||||
): JsonResponse {
|
): JsonResponse {
|
||||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||||
@@ -153,8 +155,8 @@ class ProfileController extends AbstractController
|
|||||||
return $this->json(['error' => 'No file uploaded.'], 400);
|
return $this->json(['error' => 'No file uploaded.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file->getSize() > 2 * 1024 * 1024) {
|
if ($file->getSize() > 10 * 1024 * 1024) {
|
||||||
return $this->json(['error' => 'File is too large. Maximum 2 MB.'], 400);
|
return $this->json(['error' => 'File is too large. Maximum 10 MB.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
@@ -182,6 +184,7 @@ class ProfileController extends AbstractController
|
|||||||
$mediaStorage->writeStream($newPath, $stream);
|
$mediaStorage->writeStream($newPath, $stream);
|
||||||
} catch (FilesystemException $e) {
|
} catch (FilesystemException $e) {
|
||||||
$this->logger->error('Unable to write new avatar: ' . $e->getMessage());
|
$this->logger->error('Unable to write new avatar: ' . $e->getMessage());
|
||||||
|
fclose($stream);
|
||||||
throw new RuntimeException('Unable to write new avatar: ' . $e->getMessage());
|
throw new RuntimeException('Unable to write new avatar: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
fclose($stream);
|
fclose($stream);
|
||||||
@@ -190,7 +193,7 @@ class ProfileController extends AbstractController
|
|||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'thumbUrl' => $cacheManager->generateUrl($newPath, 'avatar_thumb'),
|
'thumbUrl' => $filterService->getUrlOfFilteredImage($newPath, 'avatar_thumb'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
{% macro stat_val(value, suffix) %}
|
{% macro stat_val(value, suffix) %}
|
||||||
{%- set abbr = value >= 1000 -%}
|
{%- set abbr = value >= 1000 -%}
|
||||||
<span class="profile-stat__value"{% if abbr %} title="{{ value }}"{% endif %}>{% if abbr %}{{ (value / 1000)|round(1, 'floor') }}k{% else %}{{ value }}{% endif %}{% if suffix %}<small>{{ suffix }}</small>{% endif %}</span>
|
<span
|
||||||
|
class="profile-stat__value"{% if abbr %} title="{{ value }}"{% endif %}>{% if abbr %}{{ (value / 1000)|round(1, 'floor') }}k{% else %}{{ value }}{% endif %}{% if suffix %}
|
||||||
|
<small>{{ suffix }}</small>{% endif %}</span>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% block title %} - Profile{% endblock %}
|
{% block title %} - Profile{% endblock %}
|
||||||
@@ -31,7 +33,7 @@
|
|||||||
<div class="profile-header">
|
<div class="profile-header">
|
||||||
<div id="profile-avatar-root"
|
<div id="profile-avatar-root"
|
||||||
data-upload-url="{{ path('MineSeekerBundle_profile_avatar') }}"
|
data-upload-url="{{ path('MineSeekerBundle_profile_avatar') }}"
|
||||||
data-thumb-url="{{ app.user.avatarPath ? app.user.avatarPath|imagine_filter('avatar_thumb') : '' }}"
|
data-thumb-url="{{ app.user.avatarPath ? path('liip_imagine_filter', {path: app.user.avatarPath, filter: 'avatar_thumb'}) : '' }}"
|
||||||
data-initials="{{ app.user.username|slice(0, 2)|upper }}">
|
data-initials="{{ app.user.username|slice(0, 2)|upper }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-info">
|
<div class="profile-info">
|
||||||
@@ -138,7 +140,9 @@
|
|||||||
and (my_points > 25 or opp_points > 25)) %}
|
and (my_points > 25 or opp_points > 25)) %}
|
||||||
{% set is_anonymous = game.oppIsGuest %}
|
{% set is_anonymous = game.oppIsGuest %}
|
||||||
|
|
||||||
<div class="profile-game profile-game--{{ result }}{% if not is_finished and not is_anonymous %} profile-game--ongoing{% elseif is_anonymous %} profile-game--abandoned{% endif %}{% if loop.index0 >= 5 %} profile-game--hidden{% endif %}" data-game-index="{{ loop.index0 }}">
|
<div
|
||||||
|
class="profile-game profile-game--{{ result }}{% if not is_finished and not is_anonymous %} profile-game--ongoing{% elseif is_anonymous %} profile-game--abandoned{% endif %}{% if loop.index0 >= 5 %} profile-game--hidden{% endif %}"
|
||||||
|
data-game-index="{{ loop.index0 }}">
|
||||||
<span class="profile-game__badge">
|
<span class="profile-game__badge">
|
||||||
{% if is_finished %}
|
{% if is_finished %}
|
||||||
{{ result == 'win' ? 'Win' : (result == 'loss' ? 'Loss' : 'Draw') }}
|
{{ result == 'win' ? 'Win' : (result == 'loss' ? 'Loss' : 'Draw') }}
|
||||||
|
|||||||
@@ -28,6 +28,20 @@
|
|||||||
<link rel="icon" href="{{ asset('/images/favicon/favicon.ico') }}" type="image/x-icon">
|
<link rel="icon" href="{{ asset('/images/favicon/favicon.ico') }}" type="image/x-icon">
|
||||||
{% block metas %}{% endblock %}
|
{% block metas %}{% endblock %}
|
||||||
<title>MineSeeker{% block title %}{% endblock %}</title>
|
<title>MineSeeker{% block title %}{% endblock %}</title>
|
||||||
|
<script
|
||||||
|
defer src="https://umami.splendidbear.org/script.js"
|
||||||
|
data-website-id="825e02a9-d675-4cbd-9e68-72b98de2e4e9"
|
||||||
|
>
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
src="https://umami.splendidbear.org/recorder.js"
|
||||||
|
data-website-id="825e02a9-d675-4cbd-9e68-72b98de2e4e9"
|
||||||
|
data-sample-rate="0.15"
|
||||||
|
data-mask-level="moderate"
|
||||||
|
data-max-duration="300000"
|
||||||
|
>
|
||||||
|
</script>
|
||||||
{% block stylesheets %}{% endblock %}
|
{% block stylesheets %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user