Skip to content

Commit

Permalink
Merge 5dfe91a into 70338d5
Browse files Browse the repository at this point in the history
  • Loading branch information
seyfeb authored Dec 7, 2020
2 parents 70338d5 + 5dfe91a commit d43032b
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
[#349](https://github.com/nextcloud/cookbook/pull/349/) @christianlupus
- Optimization for SVG to reduce the filesize
[#404](https://github.com/nextcloud/cookbook/pull/404) @thembeat
- Images in recipe list are lazily loaded
[#413](https://github.com/nextcloud/cookbook/pull/413/) @seyfeb

### Fixed
- Add a min PHP restriction in the metadata
Expand Down
8 changes: 4 additions & 4 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
['name' => 'main#search', 'url' => '/api/search/{query}', 'verb' => 'GET'],
],

/* API resources */
'resources' => [
'recipe' => ['url' => '/api/recipes']
]
/* API resources */
'resources' => [
'recipe' => ['url' => '/api/recipes']
]
];
Binary file added img/recipe-thumb16.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions lib/Controller/MainController.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ public function search($query) {
't' => $this->service->getRecipeMTime($recipe['recipe_id'])
]
);
$recipes[$i]['imagePlaceholderUrl'] = $this->urlGenerator->linkToRoute(
'cookbook.recipe.image',
[
'id' => $recipe['recipe_id'],
'size' => 'thumb16'
]
);
}

return new DataResponse($recipes, 200, ['Content-Type' => 'application/json']);
Expand Down Expand Up @@ -173,6 +180,13 @@ public function category($category) {
't' => $this->service->getRecipeMTime($recipe['recipe_id'])
]
);
$recipes[$i]['imagePlaceholderUrl'] = $this->urlGenerator->linkToRoute(
'cookbook.recipe.image',
[
'id' => $recipe['recipe_id'],
'size' => 'thumb16'
]
);
}

return new DataResponse($recipes, Http::STATUS_OK, ['Content-Type' => 'application/json']);
Expand Down Expand Up @@ -200,6 +214,13 @@ public function tags($keywords) {
't' => $this->service->getRecipeMTime($recipe['recipe_id'])
]
);
$recipes[$i]['imagePlaceholderUrl'] = $this->urlGenerator->linkToRoute(
'cookbook.recipe.image',
[
'id' => $recipe['recipe_id'],
'size' => 'thumb16'
]
);
}

return new DataResponse($recipes, Http::STATUS_OK, ['Content-Type' => 'application/json']);
Expand Down
1 change: 1 addition & 0 deletions lib/Controller/RecipeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public function index() {
}
foreach ($recipes as $i => $recipe) {
$recipes[$i]['imageUrl'] = $this->urlGenerator->linkToRoute('cookbook.recipe.image', ['id' => $recipe['recipe_id'], 'size' => 'thumb']);
$recipes[$i]['imagePlaceholderUrl'] = $this->urlGenerator->linkToRoute('cookbook.recipe.image', ['id' => $recipe['recipe_id'], 'size' => 'thumb16']);
}
return new DataResponse($recipes, Http::STATUS_OK, ['Content-Type' => 'application/json']);
}
Expand Down
47 changes: 45 additions & 2 deletions lib/Service/RecipeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,10 @@ public function addRecipe($json) {
if ($recipe_folder->nodeExists('thumb.jpg')) {
$recipe_folder->get('thumb.jpg')->delete();
}

if ($recipe_folder->nodeExists('thumb16.jpg')) {
$recipe_folder->get('thumb16.jpg')->delete();
}
}

// If image data was fetched, write it to disk
Expand All @@ -748,7 +752,7 @@ public function addRecipe($json) {
$thumb_image = new Image();
$thumb_image->loadFromData($full_image_data);
$thumb_image->fixOrientation();
$thumb_image->resize(128);
$thumb_image->resize(256);
$thumb_image->centerCrop();

try {
Expand All @@ -758,6 +762,15 @@ public function addRecipe($json) {
}

$thumb_image_file->putContent($thumb_image->data());

// Create low-resolution thumbnail preview
$low_res_thumb_image = $thumb_image->resizeCopy(16);
try {
$low_res_thumb_image_file = $recipe_folder->get('thumb16.jpg');
} catch (NotFoundException $e) {
$low_res_thumb_image_file = $recipe_folder->newFile('thumb16.jpg');
}
$low_res_thumb_image_file->putContent($low_res_thumb_image->data());
}

// Write .nomedia file to avoid gallery indexing
Expand Down Expand Up @@ -1049,7 +1062,7 @@ public function getRecipeImageFileByFolderId($id, $size = 'thumb') {
if (!$size) {
$size = 'thumb';
}
if ($size !== 'full' && $size !== 'thumb') {
if ($size !== 'full' && $size !== 'thumb' && $size !== 'thumb16') {
throw new Exception('Image size "' . $size . '" not recognised');
}

Expand All @@ -1064,6 +1077,36 @@ public function getRecipeImageFileByFolderId($id, $size = 'thumb') {
$image_file = null;
$image_filename = $size . '.jpg';

if (($size == 'thumb16' || $size == 'thumb') && !$recipe_folder->nodeExists($image_filename)) {
if ($recipe_folder->nodeExists('full.jpg')) {
// Write the thumbnail
$recipe_full_image_file = $recipe_folder->get('full.jpg');
$full_image_data = $recipe_full_image_file->getContent();
$thumb_image = new Image();
$thumb_image->loadFromData($full_image_data);
$thumb_image->fixOrientation();
$thumb_image->resize(256);
$thumb_image->centerCrop();

try {
$thumb_image_file = $recipe_folder->get('thumb.jpg');
} catch (NotFoundException $e) {
$thumb_image_file = $recipe_folder->newFile('thumb.jpg');
}

$thumb_image_file->putContent($thumb_image->data());

// Create low-resolution thumbnail preview
$low_res_thumb_image = $thumb_image->resizeCopy(16);
try {
$low_res_thumb_image_file = $recipe_folder->get('thumb16.jpg');
} catch (NotFoundException $e) {
$low_res_thumb_image_file = $recipe_folder->newFile('thumb16.jpg');
}
$low_res_thumb_image_file->putContent($low_res_thumb_image->data());
}
}

$image_file = $recipe_folder->get($image_filename);

if ($image_file && $this->isImage($image_file)) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"dependencies": {
"@nextcloud/event-bus": "^1.1.4",
"@nextcloud/vue": "^1.5.0",
"lozad": "^1.16.0",
"vue": "^2.6.11",
"vue-i18n": "^8.17.1",
"vue-router": "^3.1.6",
Expand Down
16 changes: 13 additions & 3 deletions src/components/AppIndex.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<ul class="recipes">
<li v-for="(recipe, index) in filteredRecipes" :key="recipe.recipe_id" v-show="recipeVisible(index)">
<router-link :to="'/recipe/'+recipe.recipe_id">
<img v-if="recipe.imageUrl" :src="recipe.imageUrl">
<lazy-picture v-if="recipe.imageUrl"
class="recipe-thumbnail"
:lazy-src="recipe.imageUrl"
:blurred-preview-src="recipe.imagePlaceholderUrl"
:width="105" :height="105"/>
<span>{{ recipe.name }}</span>
</router-link>
</li>
Expand All @@ -15,12 +19,14 @@
</template>

<script>
import LazyPicture from './LazyPicture'
import RecipeKeyword from './RecipeKeyword'
export default {
name: 'Index',
components: {
RecipeKeyword,
LazyPicture,
RecipeKeyword
},
data () {
return {
Expand Down Expand Up @@ -164,10 +170,14 @@ ul.recipes {
box-shadow: 0 0 5px #888;
}
ul.recipes li img {
ul.recipes li .recipe-thumbnail {
position: relative;
float: left;
height: 105px;
width: 105px;
border-radius: 3px 0 0 3px;
overflow: hidden;
}
ul.recipes li span {
Expand Down
168 changes: 168 additions & 0 deletions src/components/LazyPicture.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<template>
<picture class="lazy-img" style="min-height: 1rem" :data-alt="alt"
:style="style">
<span v-if="isPreviewLoading" class="loading-indicator icon-loading"/>
<img
class="low-resolution blurred"
:class="{previewLoaded: !isPreviewLoading}"
:width="width ? width + 'px' : ''"
:height="height ? height + 'px' : ''"
/>
<img
class="full-resolution"
imageLoaded
:class="{imageLoaded: !isLoading}"
:width="width ? width + 'px' : ''"
:height="height ? height + 'px' : ''"
/>
</picture>
</template>

<script>
import lozad from 'lozad';
export default {
name: "LazyPicture",
props: {
alt: {
type: String,
default: null
},
blurredPreviewSrc: {
type: String,
default: null
},
lazySrc: {
type: String,
default: null
},
width: {
type: Number,
default: null
},
height: {
type: Number,
default: null
},
},
data() {
return {
isPreviewLoading: {
type: Boolean,
default: true,
},
isLoading: {
type: Boolean,
default: true,
},
isBlurred: {
type: Boolean,
default: true,
},
};
},
computed: {
style() {
const style = { }
if (this.width) {
style.width = `${this.width}px`
}
if (this.isLoading && this.height && !this.blurredPreviewSrc) {
style.height = 0
style.paddingTop = `${this.height}px`
}
return style;
},
},
mounted() {
let $this = this
// init lozad
const observer = lozad(this.$el, {
enableAutoReload: true,
load: function(el) {
let loadingIndicator = el.querySelector('.loading-indicator')
let imgPlaceholder = el.querySelector('.low-resolution')
let img = el.querySelector('.full-resolution')
// callback for fully-loaded image event
const onThumbnailFullyLoaded = () => {
el.removeChild(imgPlaceholder)
$this.isLoading = false
}
// callback for preview-image-loaded event
const onThumbnailPreviewLoaded = () => {
img.addEventListener('load', onThumbnailFullyLoaded)
$this.$once('hook:destroyed', () => {
img.removeEventListener('load', onThumbnailFullyLoaded)
})
img.src = $this.lazySrc
$this.isPreviewLoading = false
}
imgPlaceholder.addEventListener('load', onThumbnailPreviewLoaded)
$this.$once('hook:destroyed', () => {
imgPlaceholder.removeEventListener('load', onThumbnailPreviewLoaded)
})
imgPlaceholder.src = $this.blurredPreviewSrc
}
})
observer.observe()
},
}
</script>


<style scoped>
.lazy-img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
overflow: hidden;
}
picture .loading-indicator {
align-content: center;
display: contents;
}
picture img.blurred {
filter: blur(.5rem);
-webkit-filter: blur(.5rem);
}
picture img.low-resolution.previewLoaded {
display: inline;
-webkit-animation: fadeIn 1s linear 0s;
animation: fadeIn 1s linear 0s;
}
picture img.full-resolution {
display: none;
}
picture img.full-resolution.imageLoaded {
display: inline;
-webkit-animation: unblur 1s linear 0s;
animation: unblur 1s linear 0s;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes unblur {
from {
filter: blur(.5rem);
}
to {
filter: blur(0rem);
}
}
</style>
Loading

0 comments on commit d43032b

Please sign in to comment.