fix(persons): set person picture as optional for better ux
- use default avatar image if person image is not set - add thumbnail and medium default avatar images - set default avatar images directly in public/media folder - remove public/media's root folder from .gitignore - remove unnecessary copy:images script and cpy-cli package closes #125
2
.gitignore
vendored
@ -144,8 +144,6 @@ public/*
|
||||
!public/robots.txt
|
||||
|
||||
# public media folder
|
||||
public/media/*
|
||||
!public/media/index.html
|
||||
!public/media/podcasts
|
||||
!public/media/persons
|
||||
|
||||
|
@ -21,11 +21,11 @@ class ActivityPub extends ActivityPubBase
|
||||
* Default avatar and cover images
|
||||
* --------------------------------------------------------------------
|
||||
*/
|
||||
public string $defaultAvatarImagePath = 'assets/images/castopod-avatar-default.jpg';
|
||||
public string $defaultAvatarImagePath = 'media/castopod-avatar-default_thumbnail.jpg';
|
||||
|
||||
public string $defaultAvatarImageMimetype = 'image/jpeg';
|
||||
|
||||
public string $defaultCoverImagePath = 'assets/images/castopod-cover-default.jpg';
|
||||
public string $defaultCoverImagePath = 'media/castopod-cover-default.jpg';
|
||||
|
||||
public string $defaultCoverImageMimetype = 'image/jpeg';
|
||||
}
|
||||
|
@ -81,11 +81,15 @@ class PersonController extends BaseController
|
||||
'full_name' => $this->request->getPost('full_name'),
|
||||
'unique_name' => $this->request->getPost('unique_name'),
|
||||
'information_url' => $this->request->getPost('information_url'),
|
||||
'image' => new Image($this->request->getFile('image')),
|
||||
'created_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
]);
|
||||
|
||||
$imageFile = $this->request->getFile('image');
|
||||
if ($imageFile !== null && $imageFile->isValid()) {
|
||||
$person->image = new Image($imageFile);
|
||||
}
|
||||
|
||||
$personModel = new PersonModel();
|
||||
|
||||
if (! $personModel->insert($person)) {
|
||||
@ -129,6 +133,7 @@ class PersonController extends BaseController
|
||||
$this->person->full_name = $this->request->getPost('full_name');
|
||||
$this->person->unique_name = $this->request->getPost('unique_name');
|
||||
$this->person->information_url = $this->request->getPost('information_url');
|
||||
|
||||
$imageFile = $this->request->getFile('image');
|
||||
if ($imageFile !== null && $imageFile->isValid()) {
|
||||
$this->person->image = new Image($imageFile);
|
||||
|
@ -45,12 +45,14 @@ class AddPersons extends Migration
|
||||
'image_path' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
],
|
||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||
'image_mimetype' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 13,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
|
@ -47,8 +47,8 @@ class Person extends Entity
|
||||
'full_name' => 'string',
|
||||
'unique_name' => 'string',
|
||||
'information_url' => '?string',
|
||||
'image_path' => 'string',
|
||||
'image_mimetype' => 'string',
|
||||
'image_path' => '?string',
|
||||
'image_mimetype' => '?string',
|
||||
'podcast_id' => '?integer',
|
||||
'episode_id' => '?integer',
|
||||
'created_by' => 'integer',
|
||||
@ -58,8 +58,12 @@ class Person extends Entity
|
||||
/**
|
||||
* Saves a picture in `public/media/persons/`
|
||||
*/
|
||||
public function setImage(Image $image): static
|
||||
public function setImage(?Image $image = null): static
|
||||
{
|
||||
if ($image === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('media');
|
||||
|
||||
// Save image
|
||||
@ -73,6 +77,10 @@ class Person extends Entity
|
||||
|
||||
public function getImage(): Image
|
||||
{
|
||||
if ($this->attributes['image_path'] === null) {
|
||||
return new Image(null, '/castopod-avatar-default.jpg', 'image/jpeg');
|
||||
}
|
||||
|
||||
return new Image(null, $this->attributes['image_path'], $this->attributes['image_mimetype']);
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
$podcastNamespace,
|
||||
);
|
||||
|
||||
$personElement->addAttribute('img', $person->image->large_url);
|
||||
$personElement->addAttribute('img', $person->image->medium_url);
|
||||
|
||||
if ($person->information_url !== null) {
|
||||
$personElement->addAttribute('href', $person->information_url);
|
||||
@ -298,7 +298,7 @@ if (! function_exists('get_rss_feed')) {
|
||||
htmlspecialchars(lang("PersonsTaxonomy.persons.{$role->group}.label", [], 'en')),
|
||||
);
|
||||
|
||||
$personElement->addAttribute('img', $person->image->large_url);
|
||||
$personElement->addAttribute('img', $person->image->medium_url);
|
||||
|
||||
if ($person->information_url !== null) {
|
||||
$personElement->addAttribute('href', $person->information_url);
|
||||
|
@ -19,6 +19,9 @@ return [
|
||||
'form' => [
|
||||
'identity_section_title' => 'Identity',
|
||||
'identity_section_subtitle' => 'Who is working on the podcast',
|
||||
'image' => 'Picture',
|
||||
'image_size_hint' =>
|
||||
'Image must be squared with at least 400px wide and tall.',
|
||||
'full_name' => 'Full name',
|
||||
'full_name_hint' => 'This is the full name or alias of the person.',
|
||||
'unique_name' => 'Unique name',
|
||||
@ -26,9 +29,6 @@ return [
|
||||
'information_url' => 'Information URL',
|
||||
'information_url_hint' =>
|
||||
'Url to a relevant resource of information about the person, such as a homepage or third-party profile platform.',
|
||||
'image' => 'Picture, avatar, image',
|
||||
'image_size_hint' =>
|
||||
'Image must be squared with at least 400px wide and tall.',
|
||||
'submit_create' => 'Create person',
|
||||
'submit_edit' => 'Save person',
|
||||
],
|
||||
|
@ -19,6 +19,9 @@ return [
|
||||
'form' => [
|
||||
'identity_section_title' => 'Identité',
|
||||
'identity_section_subtitle' => 'Qui intervient sur le podcast',
|
||||
'image' => 'Photo',
|
||||
'image_size_hint' =>
|
||||
'L’image doit être carrée et avoir au moins 400px de largeur et de hauteur.',
|
||||
'full_name' => 'Nom complet',
|
||||
'full_name_hint' => 'Le nom complet ou le pseudonyme de l’intervenant',
|
||||
'unique_name' => 'Nom unique',
|
||||
@ -26,9 +29,6 @@ return [
|
||||
'information_url' => 'Adresse d’information',
|
||||
'information_url_hint' =>
|
||||
'URL pointant vers des informations relatives à l’intervenant, telle qu’une page personnelle ou une page de profil sur une plateforme tierce.',
|
||||
'image' => 'Photo, avatar, image',
|
||||
'image_size_hint' =>
|
||||
'L’image doit être carrée et avoir au moins 400px de largeur et de hauteur.',
|
||||
'submit_create' => 'Créer l’intervenant',
|
||||
'submit_edit' => 'Enregistrer l’intervenant',
|
||||
],
|
||||
|
@ -63,7 +63,6 @@ class PersonModel extends Model
|
||||
'full_name' => 'required',
|
||||
'unique_name' =>
|
||||
'required|regex_match[/^[a-z0-9\-]{1,191}$/]|is_unique[persons.unique_name,id,{id}]',
|
||||
'image_path' => 'required',
|
||||
'created_by' => 'required',
|
||||
'updated_by' => 'required',
|
||||
];
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@ -22,6 +22,18 @@
|
||||
lang('Person.form.identity_section_subtitle'),
|
||||
) ?>
|
||||
|
||||
<?= form_label(lang('Person.form.image'), 'image') ?>
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
'name' => 'image',
|
||||
'class' => 'form-input',
|
||||
'type' => 'file',
|
||||
'accept' => '.jpg,.jpeg,.png',
|
||||
]) ?>
|
||||
<small class="mb-4 text-gray-600"><?= lang(
|
||||
'Person.form.image_size_hint',
|
||||
) ?></small>
|
||||
|
||||
<?= form_label(
|
||||
lang('Person.form.full_name'),
|
||||
'full_name',
|
||||
@ -66,19 +78,6 @@
|
||||
'value' => old('information_url'),
|
||||
]) ?>
|
||||
|
||||
<?= form_label(lang('Person.form.image'), 'image') ?>
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
'name' => 'image',
|
||||
'class' => 'form-input',
|
||||
'required' => 'required',
|
||||
'type' => 'file',
|
||||
'accept' => '.jpg,.jpeg,.png',
|
||||
]) ?>
|
||||
<small class="mb-4 text-gray-600"><?= lang(
|
||||
'Person.form.image_size_hint',
|
||||
) ?></small>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= button(
|
||||
|
@ -23,6 +23,18 @@
|
||||
"<img src=\"{$person->image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-32 h-32 mt-3 rounded\" />",
|
||||
) ?>
|
||||
|
||||
<?= form_label(lang('Person.form.image'), 'image') ?>
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
'name' => 'image',
|
||||
'class' => 'form-input',
|
||||
'type' => 'file',
|
||||
'accept' => '.jpg,.jpeg,.png',
|
||||
]) ?>
|
||||
<small class="mb-4 text-gray-600"><?= lang(
|
||||
'Person.form.image_size_hint',
|
||||
) ?></small>
|
||||
|
||||
<?= form_label(
|
||||
lang('Person.form.full_name'),
|
||||
'full_name',
|
||||
@ -67,18 +79,6 @@
|
||||
'value' => old('information_url', $person->information_url),
|
||||
]) ?>
|
||||
|
||||
<?= form_label(lang('Person.form.image'), 'image') ?>
|
||||
<?= form_input([
|
||||
'id' => 'image',
|
||||
'name' => 'image',
|
||||
'class' => 'form-input',
|
||||
'type' => 'file',
|
||||
'accept' => '.jpg,.jpeg,.png',
|
||||
]) ?>
|
||||
<small class="mb-4 text-gray-600"><?= lang(
|
||||
'Person.form.image_size_hint',
|
||||
) ?></small>
|
||||
|
||||
<?= form_section_close() ?>
|
||||
|
||||
<?= button(
|
||||
|
@ -211,13 +211,12 @@ You do not wish to use the VSCode devcontainer? No problem!
|
||||
3. Generate static assets:
|
||||
|
||||
```bash
|
||||
# build all assets at once
|
||||
# build all static assets at once
|
||||
npm run build:static
|
||||
|
||||
# generate/copy specific assets
|
||||
# build specific assets
|
||||
npm run build:icons
|
||||
npm run build:svg
|
||||
npm run copy:images
|
||||
```
|
||||
|
||||
> **Note:**
|
||||
|
9740
package-lock.json
generated
@ -12,10 +12,9 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"serve": "vite preview",
|
||||
"build:static": "npm run build:icons && npm run build:svg && npm run copy:images",
|
||||
"build:static": "npm run build:icons && npm run build:svg",
|
||||
"build:icons": "svgo -f app/Resources/icons -o public/assets/icons -r --config=./.svgo.icons.js",
|
||||
"build:svg": "svgo -f app/Resources/images -o public/assets/images -r --config=./.svgo.js",
|
||||
"copy:images": "cpy app/Resources/images/*.jpg public/assets/images",
|
||||
"lint": "eslint --ext js,ts app/Resources",
|
||||
"lint:fix": "eslint --ext js,ts app/Resources --fix",
|
||||
"lint:css": "stylelint \"app/Resources/**/*.css\"",
|
||||
@ -55,7 +54,6 @@
|
||||
"@types/prosemirror-view": "^1.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.5",
|
||||
"@typescript-eslint/parser": "^4.28.5",
|
||||
"cpy-cli": "^3.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^5.0.7",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
|
BIN
public/media/castopod-avatar-default.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/media/castopod-avatar-default_medium.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/media/castopod-avatar-default_thumbnail.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
BIN
public/media/site/favicon.755caa50.ico
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/media/site/icon-180.755caa50.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/media/site/icon-192.755caa50.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/media/site/icon-512.755caa50.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
public/media/site/icon-64.755caa50.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
public/media/site/icon.jpg
Normal file
After Width: | Height: | Size: 214 KiB |
BIN
public/media/site/icon.png
Normal file
After Width: | Height: | Size: 161 KiB |