Skip to content
This repository has been archived by the owner on Mar 3, 2024. It is now read-only.

Commit

Permalink
Redesigned tag list page
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-schwartz committed Oct 7, 2018
1 parent e114f3e commit cb3d863
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 66 deletions.
4 changes: 2 additions & 2 deletions config/packages/framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ framework:

# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: ~
# session:
# handler_id: ~

#esi: true
#fragments: true
Expand Down
9 changes: 9 additions & 0 deletions public/inventory.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ h1 {
max-width: 200px;
margin: 0 1rem 1rem 0;
}

.tag-list .card {
width: 202px;
}

.tag-list .no-image {
background-color: #c0c0c0;
height: 200px;
}
10 changes: 5 additions & 5 deletions src/Controller/Inventory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use App\Entity\Tag;
use App\Service\DocumentStorage;
use App\Service\ImageStorage;
use Symfony\Component\HttpFoundation\Response;

class Inventory extends Controller
{
Expand Down Expand Up @@ -236,6 +237,8 @@ private function deleteImages(Request $request, InventoryItem $item)

/**
* GET image content; POST to delete
*
* Query string parameters "w" and "h" can be used to get a scaled version. Original images will be scaled as needed.
*/
public function image(Request $request, $id, $filename)
{
Expand All @@ -247,12 +250,9 @@ public function image(Request $request, $id, $filename)
$this->images->deleteItemImage($item, $filename);
return new JsonResponse(['success' => 1]);
} else {
if ($width = $request->query->get('w')) {
$filename = str_replace('.', 'w' . $width . '.', $filename);
}
$path = $this->images->getItemImagePath($item) . DIRECTORY_SEPARATOR . $filename;
$path = $this->images->getFilePath($item, $filename, $request->query->get('w'), $request->query->get('h'));
if (file_exists($path)) {
return new BinaryFileResponse($path);
return new BinaryFileResponse($path, Response::HTTP_OK, ['Cache-Control' => 'max-age=14400']);
} else {
throw $this->createNotFoundException('Image not found');
}
Expand Down
24 changes: 21 additions & 3 deletions src/Controller/Tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@
use Symfony\Component\HttpFoundation\Request;

use App\Service\DocumentStorage;
use App\Service\ImageStorage;

class Tag extends Controller
{
/** @var DocumentStorage */
protected $docs;

public function __construct(DocumentStorage $docs)
/** @var ImageStorage */
protected $images;

public function __construct(DocumentStorage $docs, ImageStorage $images)
{
$this->docs = $docs;
$this->images = $images;
}

/**
Expand All @@ -24,7 +29,20 @@ public function __construct(DocumentStorage $docs)
*/
public function listTags(string $category)
{
$tags = $this->docs->getTags($category, ['count', 'name']);
return $this->render('tag/list.html.twig', ['tags' => $tags]);
$tags = $this->docs->getTags($category, ['count', 'name'])->toArray();
$images = [];
foreach ($tags as $tag) {
// Get a random image associated with the tag
$images[$tag->getName()] = null;
$item = $this->docs->getRandomInventoryItemByTag($category, $tag->getName());
if ($item) {
$itemImages = $this->images->getItemImages($item);
if ($count = count($itemImages)) {
$rand = rand(0, $count - 1);
$images[$tag->getName()] = ['itemid' => $item->getId(), 'filename' => $itemImages[$rand]];
}
}
}
return $this->render('tag/list.html.twig', ['tags' => $tags, 'images' => $images]);
}
}
46 changes: 33 additions & 13 deletions src/Service/DocumentStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ public function searchInventoryItems(string $query) : iterable
]);
}

/**
* Get an inventory item
*
* @return App\Entity\InventoryItem
*/
public function getInventoryItem(string $id) : ?InventoryItem
{
$inventory = $this->getInventoryCollection();
return $inventory->findOne(['_id' => new ObjectId("$id"), 'deleted' => false]);
}

/**
* Get inventory items by tag name
*
Expand All @@ -93,25 +104,34 @@ public function searchInventoryItems(string $query) : iterable
*/
public function getInventoryItemsByTag(string $category, string $tag) : iterable
{
return $this->getInventoryCollection()->find([
$category => [
'$regex' => '^' . $tag . '$',
'$options' => 'i'
],
'deleted' => false
],
['sort' => ['name' => 1]]);
return $this->getInventoryCollection()->find(
[
$category => [
'$regex' => '^' . $tag . '$',
'$options' => 'i'
],
'deleted' => false
],
['sort' => ['name' => 1]]
);
}

/**
* Get an inventory item
* Get one random inventory item by tag name
*
* @return App\Entity\InventoryItem
* @param string $category One of Tag::CATEGORY_*
* @param string $tag Tag name
* @return MongoDB\Driver\Cursor
*/
public function getInventoryItem(string $id) : ?InventoryItem
public function getRandomInventoryItemByTag(string $category, string $tag) : ?InventoryItem
{
$inventory = $this->getInventoryCollection();
return $inventory->findOne(['_id' => new ObjectId("$id"), 'deleted' => false]);
return $this->getInventoryCollection()->findOne([
$category => [
'$regex' => '^' . $tag . '$',
'$options' => 'i'
],
'deleted' => false
]);
}

/**
Expand Down
114 changes: 95 additions & 19 deletions src/Service/ImageStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
class ImageStorage
{
const WIDTH_SMALL = 200;
const HEIGHT_SMALL = 200;

/** @var string */
protected $basePath;
Expand All @@ -30,6 +31,8 @@ public function getItemImagePath(InventoryItem $item)
}

/**
* Save images and their resized versions during upload.
*
* @param InventoryItem $item
* @param UploadedFile[] $files
*/
Expand All @@ -51,24 +54,19 @@ public function saveItemImages(InventoryItem $item, array $files)
}
$originalFilename = $time . 'i' . $count . '.' . $extension;
$file->move($itemPath, $originalFilename);

$resizer = new ImageResize($itemPath . DIRECTORY_SEPARATOR . $originalFilename);
$resizer->resizeToWidth(self::WIDTH_SMALL);
$resizer->save(
$itemPath . DIRECTORY_SEPARATOR . $time . 'i' . $count . 'w' . self::WIDTH_SMALL . '.' . $extension
);
$this->resizeToWidth($item, $originalFilename, self::WIDTH_SMALL);
$this->resizeToWidthAndHeight($item, $originalFilename, self::WIDTH_SMALL, self::HEIGHT_SMALL);
$count++;
}
}

/**
* Get image file names associated with an item
* Get image file names associated with an item. This returns only the unscaled files.
*
* @param InventoryItem $item
* @param int $width One of WIDTH_* (optional)
* @return string[] Array of image file names (excluding path)
*/
public function getItemImages(InventoryItem $item, integer $width = null) : array
public function getItemImages(InventoryItem $item) : array
{
$images = [];
$path = $this->getItemImagePath($item);
Expand All @@ -77,15 +75,9 @@ public function getItemImages(InventoryItem $item, integer $width = null) : arra
foreach ($iter as $file) {
if (!$file->isDot()) {
$name = $file->getFilename();
if ($width) {
if (strpos($name, 'w' . $width) !== false) {
$images[] = $name;
}
} else {
$nameParts = explode('.', $name);
if (strpos($nameParts[0], 'w') === false) {
$images[] = $name;
}
$nameParts = explode('.', $name);
if (strpos($nameParts[0], 'w') === false) {
$images[] = $name;
}
}
}
Expand All @@ -94,6 +86,36 @@ public function getItemImages(InventoryItem $item, integer $width = null) : arra
return $images;
}

/**
* Get the full path to an item image file. Generate scaled image as needed.
*
* @param InventoryItem $item
* @param string $filename The file name of the unscaled image
* @param int|null $width
* @param int|null $height
* @return string
*/
public function getFilePath(InventoryItem $item, string $filename, int $width = null, int $height = null)
{
$unscaledFilename = $filename;
if ($width && $height) {
$filename = $this->getFilenameWidthHeight($unscaledFilename, $width, $height);
} elseif ($width) {
$filename = $this->getFilenameWidth($filename, $width);
}
$path = $this->getItemImagePath($item) . DIRECTORY_SEPARATOR . $filename;
if (!file_exists($path)) {
if ($width && $height) {
$this->resizeToWidthAndHeight($item, $unscaledFilename, $width, $height);
} elseif ($width) {
$this->resizeToWidth($item, $unscaledFilename, $width);
} else {
return '';
}
}
return $path;
}

/**
* Remove an item's image from storage
*
Expand All @@ -104,11 +126,65 @@ public function deleteItemImage(InventoryItem $item, string $filename)
{
$path = $this->getItemImagePath($item);
$files = [$filename];
$files[] = str_replace('.', 'w' . self::WIDTH_SMALL . '.', $filename);
// Also delete any scaled images
$files[] = $this->getFilenameWidth($filename, self::WIDTH_SMALL);
foreach ($files as $filename) {
if (file_exists($path . DIRECTORY_SEPARATOR . $filename)) {
unlink($path . DIRECTORY_SEPARATOR . $filename);
}
}
}

/**
* Resize an unscaled image to a width.
*
* @param InventoryItem $item
* @param string $filename
* @param int $width
*/
protected function resizeToWidth(InventoryItem $item, string $filename, int $width)
{
$itemPath = $this->getItemImagePath($item);
$resizer = new ImageResize($itemPath . DIRECTORY_SEPARATOR . $filename);
$resizer->resizeToWidth($width, true);
$resizer->save(
$itemPath . DIRECTORY_SEPARATOR . $this->getFilenameWidth($filename, $width)
);
}

/**
* Resize an unscaled image to a width and height. Image will be cropped to fit in the box.
*
* @param InventoryItem $item
* @param string $filename
* @param int $width
* @param int $height
*/
protected function resizeToWidthAndHeight(InventoryItem $item, string $filename, int $width, int $height)
{
$itemPath = $this->getItemImagePath($item);
$resizer = new ImageResize($itemPath . DIRECTORY_SEPARATOR . $filename);
$resizer->crop($width, $height);
$resizer->save(
$itemPath . DIRECTORY_SEPARATOR . $this->getFilenameWidthHeight($filename, $width, $height)
);
}

/**
* Get a filename for a width based on the original filename
*/
protected function getFilenameWidth(string $filename, int $width)
{
$fileparts = explode('.', $filename);
return $fileparts[0] . 'w' . $width . '.' . $fileparts[1];
}

/**
* Get a filename for a width and height based on the original filename
*/
protected function getFilenameWidthHeight(string $filename, int $width, int $height)
{
$fileparts = explode('.', $filename);
return $fileparts[0] . 'w' . $width . 'h' . $height . '.' . $fileparts[1];
}
}
24 changes: 10 additions & 14 deletions templates/inventory/list.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

{% block title %}
{% if breadcrumb %}
{# TODO: Link "items" while stripping tags for base title block #}
{# TODO: Link "items" while stripping tags in base title block #}
{# <a href="{{ path('inventory_list') }}">Items</a> > {{ breadcrumb }} #}
Items > {{ breadcrumb }}
Items &raquo; {{ breadcrumb }}
{% else %}
Items
{% endif %}
Expand Down Expand Up @@ -45,18 +45,14 @@
</tr>
{% endfor %}

{% if total_quantity or total_value %}
<tfoot>
<tr>
<th scope="col"></th>
<th scope="col" class="d-none d-md-table-cell"></th>
<th scope="col" class="text-right">{{ total_quantity }}</th>
<th scope="col" class="text-right">{{ total_value|localizedcurrency('USD') }}
</tr>
</tfoot>
{% endif %}
<tfoot>
<tr>
<td><a class="btn btn-primary" href="{{ path('inventory_add') }}?return_to=list">Add</a></td>
<th scope="col" class="d-none d-md-table-cell"></th>
<th scope="col" class="text-right">{% if total_quantity %}{{ total_quantity }}{% endif %}</th>
<th scope="col" class="text-right">{% if total_value %}{{ total_value|localizedcurrency('USD') }}{% endif %}</td>
</tr>
</tfoot>
</tbody>
</table>

<a class="btn btn-primary" href="{{ path('inventory_add') }}?return_to=list">Add</a>
{% endblock %}
Loading

0 comments on commit cb3d863

Please sign in to comment.