Use components for photo details, list, mosaic and tile view #15

This commit is contained in:
Michael Mayer 2019-05-19 17:52:23 +02:00
parent f9c553acb4
commit e855262bb2
8 changed files with 322 additions and 238 deletions

View file

@ -134,228 +134,11 @@
<v-icon>delete</v-icon> <v-icon>delete</v-icon>
</v-btn> </v-btn>
</v-speed-dial> </v-speed-dial>
<v-data-table
:headers="listColumns"
:items="results"
hide-actions
class="elevation-1"
v-if="query.view === 'list'"
select-all
disable-initial-sort
item-key="ID"
v-model="selected"
:no-data-text="'No photos matched your search'"
>
<template slot="items" slot-scope="props">
<td>
<v-checkbox
v-model="props.selected"
primary
hide-details
></v-checkbox>
</td>
<td>{{ props.item.PhotoTitle }}</td>
<td>{{ props.item.TakenAt | moment('DD/MM/YYYY hh:mm:ss') }}</td>
<td>{{ props.item.LocCity }}</td>
<td>{{ props.item.LocCountry }}</td>
<td>{{ props.item.CameraMake }} {{ props.item.CameraModel }}</td>
<td>{{ props.item.PhotoFavorite ? 'Yes' : 'No' }}</td>
</template>
</v-data-table>
<v-container grid-list-xs fluid class="pa-0" v-if="query.view === 'details'"> <app-photo-tiles v-if="query.view === 'tiles'" :photos="results" :open="openPhoto" :select="selectPhoto" :like="likePhoto"></app-photo-tiles>
<v-card v-if="results.length === 0"> <app-photo-mosaic v-if="query.view === 'mosaic'" :photos="results" :open="openPhoto" :select="selectPhoto" :like="likePhoto"></app-photo-mosaic>
<v-card-title primary-title> <app-photo-details v-if="query.view === 'details'" :photos="results" :open="openPhoto" :select="selectPhoto" :like="likePhoto"></app-photo-details>
<div> <app-photo-list v-if="query.view === 'list'" :photos="results" :selected-photos="selected" :open="openPhoto" :select="selectPhoto" :like="likePhoto"></app-photo-list>
<h3 class="headline mb-3">No photos matched your search</h3>
<div>Try using other terms and search options such as category, country and camera.</div>
</div>
</v-card-title>
</v-card>
<v-layout row wrap>
<v-flex
v-for="(photo, index) in results"
:key="photo.ID"
xs12 sm6 md4 lg3 d-flex
>
<v-hover>
<v-card tile slot-scope="{ hover }"
:dark="photo.selected"
:class="photo.selected ? 'elevation-14 ma-1' : 'elevation-2 ma-2'">
<v-img
:src="photo.getThumbnailUrl('tile_500')"
aspect-ratio="1"
v-bind:class="{ selected: photo.selected }"
style="cursor: pointer"
class="grey lighten-2"
@click="openPhoto(index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
</v-layout>
<v-btn v-if="hover || photo.selected" :flat="!hover" icon large absolute
:ripple="false" style="right: 4px; bottom: 4px;"
@click.stop.prevent="selectPhoto(photo)">
<v-icon v-if="photo.selected" color="white">check_box</v-icon>
<v-icon v-else color="white">check_box_outline_blank</v-icon>
</v-btn>
<v-btn v-if="hover || photo.PhotoFavorite" :flat="!hover" icon large absolute
:ripple="false" style="bottom: 4px; left: 4px"
@click.stop.prevent="likePhoto(photo)">
<v-icon v-if="photo.PhotoFavorite" color="white">favorite
</v-icon>
<v-icon v-else color="white">favorite_border</v-icon>
</v-btn>
</v-img>
<v-card-title primary-title class="pa-3">
<div>
<h3 class="subheading mb-2" :title="photo.PhotoTitle">{{ photo.PhotoTitle |
truncate(80) }}</h3>
<div class="caption">
<v-icon size="14">date_range</v-icon>
{{ photo.TakenAt | moment('DD/MM/YYYY hh:mm:ss') }}
<br/>
<v-icon size="14">photo_camera</v-icon>
{{ photo.getCamera() }}
<br/>
<v-icon size="14">location_on</v-icon>
<span class="link" :title="photo.getFullLocation()"
@click.stop="openLocation(photo)">{{ photo.getLocation() }}</span>
</div>
</div>
</v-card-title>
</v-card>
</v-hover>
</v-flex>
</v-layout>
</v-container>
<v-container grid-list-xs fluid class="pa-0" v-if="query.view === 'tiles'">
<v-card v-if="results.length === 0">
<v-card-title primary-title>
<div>
<h3 class="headline mb-3">No photos matched your search</h3>
<div>Try using other terms and search options such as category, country and camera.</div>
</div>
</v-card-title>
</v-card>
<v-layout row wrap>
<v-flex
v-for="(photo, index) in results"
:key="photo.ID"
xs12 sm6 md3 lg2 d-flex
v-bind:class="{ selected: photo.selected }"
>
<v-hover>
<v-card tile slot-scope="{ hover }"
:dark="photo.selected"
:class="photo.selected ? 'elevation-14 ma-1' : hover ? 'elevation-6 ma-2' : 'elevation-2 ma-2'">
<v-img :src="photo.getThumbnailUrl('tile_500')"
aspect-ratio="1"
class="grey lighten-2"
style="cursor: pointer"
@click="openPhoto(index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate
color="grey lighten-5"></v-progress-circular>
</v-layout>
<v-btn v-if="hover || photo.selected" :flat="!hover" icon large absolute
:ripple="false" style="right: 4px; bottom: 4px;"
@click.stop.prevent="selectPhoto(photo)">
<v-icon v-if="photo.selected" color="white">check_box</v-icon>
<v-icon v-else color="white">check_box_outline_blank</v-icon>
</v-btn>
<v-btn v-if="hover || photo.PhotoFavorite" :flat="!hover" icon large absolute
:ripple="false" style="bottom: 4px; left: 4px"
@click.stop.prevent="likePhoto(photo)">
<v-icon v-if="photo.PhotoFavorite" color="white">favorite</v-icon>
<v-icon v-else color="white">favorite_border</v-icon>
</v-btn>
</v-img>
</v-card>
</v-hover>
</v-flex>
</v-layout>
</v-container>
<v-container grid-list-xs fluid class="pa-0" v-if="query.view === 'mosaic'">
<v-card v-if="results.length === 0">
<v-card-title primary-title>
<div>
<h3 class="headline mb-3">No photos matched your search</h3>
<div>Try using other terms and search options such as category, country and camera.</div>
</div>
</v-card-title>
</v-card>
<v-layout row wrap>
<v-flex
v-for="(photo, index) in results"
:key="photo.ID"
xs4 sm3 md2 lg1 d-flex
v-bind:class="{ selected: photo.selected }"
>
<v-hover>
<v-card tile slot-scope="{ hover }"
:dark="photo.selected"
:class="photo.selected ? 'elevation-14 ma-1' : hover ? 'elevation-6 ma-2' : 'elevation-2 ma-2'">
<v-img :src="photo.getThumbnailUrl('tile_224')"
aspect-ratio="1"
class="grey lighten-2"
style="cursor: pointer"
@click="openPhoto(index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate
color="grey lighten-5"></v-progress-circular>
</v-layout>
<v-btn v-if="hover || photo.selected" :flat="!hover" icon large absolute
:ripple="false" style="right: 1px; bottom: 1px;"
@click.stop.prevent="selectPhoto(photo)">
<v-icon v-if="photo.selected" color="white">check_box</v-icon>
<v-icon v-else color="white">check_box_outline_blank</v-icon>
</v-btn>
<v-btn v-if="hover || photo.PhotoFavorite" :flat="!hover" icon large absolute
:ripple="false" style="bottom: 1px; left: 1px"
@click.stop.prevent="likePhoto(photo)">
<v-icon v-if="photo.PhotoFavorite" color="white">favorite</v-icon>
<v-icon v-else color="white">favorite_border</v-icon>
</v-btn>
</v-img>
</v-card>
</v-hover>
</v-flex>
</v-layout>
</v-container>
<v-snackbar <v-snackbar
v-model="snackbarVisible" v-model="snackbarVisible"
@ -609,14 +392,6 @@
{value: 'imported', text: 'Recently imported'}, {value: 'imported', text: 'Recently imported'},
], ],
}, },
'listColumns': [
{text: 'Title', value: 'PhotoTitle'},
{text: 'Taken At', value: 'TakenAt'},
{text: 'City', value: 'LocCity'},
{text: 'Country', value: 'LocCountry'},
{text: 'Camera', value: 'CameraModel'},
{text: 'Favorite', value: 'PhotoFavorite'},
],
'view': view, 'view': view,
'loadMoreDisabled': true, 'loadMoreDisabled': true,
'pageSize': 60, 'pageSize': 60,

View file

@ -12,10 +12,6 @@
</transition> </transition>
</template> </template>
<script> <script>
/**
* Helpers
*/
function clamp(n, min, max) { function clamp(n, min, max) {
if (n < min) { if (n < min) {
return min return min
@ -27,22 +23,24 @@
} }
let queue = (() => { let queue = (() => {
let pending = [] let pending = [];
function next() { function next() {
let fn = pending.shift() let fn = pending.shift();
if (fn) { if (fn) {
fn(next) fn(next)
} }
} }
return fn => { return fn => {
pending.push(fn) pending.push(fn);
if (pending.length === 1) { if (pending.length === 1) {
next() next()
} }
} }
})() })();
export default { export default {
name: 'app-loading-bar', name: 'app-loading-bar',

View file

@ -0,0 +1,105 @@
<template>
<v-container grid-list-xs fluid class="pa-0">
<v-card v-if="photos.length === 0">
<v-card-title primary-title>
<div>
<h3 class="headline mb-3">No photos matched your search</h3>
<div>Try using other terms and search options such as category, country and camera.</div>
</div>
</v-card-title>
</v-card>
<v-layout row wrap>
<v-flex
v-for="(photo, index) in photos"
:key="photo.ID"
xs12 sm6 md4 lg3 d-flex
>
<v-hover>
<v-card tile slot-scope="{ hover }"
:dark="photo.selected"
:class="photo.selected ? 'elevation-14 ma-1' : 'elevation-2 ma-2'">
<v-img
:src="photo.getThumbnailUrl('tile_500')"
aspect-ratio="1"
v-bind:class="{ selected: photo.selected }"
style="cursor: pointer"
class="grey lighten-2"
@click="open(index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
</v-layout>
<v-btn v-if="hover || photo.selected" :flat="!hover" icon large absolute
:ripple="false" style="right: 4px; bottom: 4px;"
@click.stop.prevent="select(photo)">
<v-icon v-if="photo.selected" color="white">check_box</v-icon>
<v-icon v-else color="white">check_box_outline_blank</v-icon>
</v-btn>
<v-btn v-if="hover || photo.PhotoFavorite" :flat="!hover" icon large absolute
:ripple="false" style="bottom: 4px; left: 4px"
@click.stop.prevent="like(photo)">
<v-icon v-if="photo.PhotoFavorite" color="white">favorite
</v-icon>
<v-icon v-else color="white">favorite_border</v-icon>
</v-btn>
</v-img>
<v-card-title primary-title class="pa-3">
<div>
<h3 class="subheading mb-2" :title="photo.PhotoTitle">{{ photo.PhotoTitle |
truncate(80) }}</h3>
<div class="caption">
<v-icon size="14">date_range</v-icon>
{{ photo.TakenAt | moment('DD/MM/YYYY hh:mm:ss') }}
<br/>
<v-icon size="14">photo_camera</v-icon>
{{ photo.getCamera() }}
<br/>
<v-icon size="14">location_on</v-icon>
<span class="link" :title="photo.getFullLocation()"
@click.stop="openLocation(photo)">{{ photo.getLocation() }}</span>
</div>
</div>
</v-card-title>
</v-card>
</v-hover>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
name: 'app-photo-details',
props: {
photos: Array,
open: Function,
select: Function,
like: Function,
},
methods: {
openLocation(photo) {
if (photo.PhotoLat && photo.PhotoLong) {
this.$router.push({name: 'Places', query: {lat: photo.PhotoLat, long: photo.PhotoLong}});
} else if (photo.LocName) {
this.$router.push({name: 'Places', query: {q: photo.LocName}});
} else if (photo.LocCity) {
this.$router.push({name: 'Places', query: {q: photo.LocCity}});
} else if (photo.LocCountry) {
this.$router.push({name: 'Places', query: {q: photo.LocCountry}});
} else {
this.$router.push({name: 'Places', query: {q: photo.CountryName}});
}
},
}
};
</script>

View file

@ -0,0 +1,55 @@
<template>
<v-data-table
:headers="listColumns"
:items="photos"
hide-actions
class="elevation-1"
select-all
disable-initial-sort
item-key="ID"
v-model="selectedPhotos"
:no-data-text="'No photos matched your search'"
>
<template slot="items" slot-scope="props">
<td>
<v-checkbox
v-model="props.selected"
primary
hide-details
></v-checkbox>
</td>
<td>{{ props.item.PhotoTitle }}</td>
<td>{{ props.item.TakenAt | moment('DD/MM/YYYY hh:mm:ss') }}</td>
<td>{{ props.item.LocCity }}</td>
<td>{{ props.item.LocCountry }}</td>
<td>{{ props.item.CameraMake }} {{ props.item.CameraModel }}</td>
<td>{{ props.item.PhotoFavorite ? 'Yes' : 'No' }}</td>
</template>
</v-data-table>
</template>
<script>
export default {
name: 'app-photo-list',
props: {
photos: Array,
selectedPhotos: Array,
open: Function,
select: Function,
like: Function,
},
data() {
return {
'listColumns': [
{text: 'Title', value: 'PhotoTitle'},
{text: 'Taken At', value: 'TakenAt'},
{text: 'City', value: 'LocCity'},
{text: 'Country', value: 'LocCountry'},
{text: 'Camera', value: 'CameraModel'},
{text: 'Favorite', value: 'PhotoFavorite'},
],
};
},
methods: {
}
};
</script>

View file

@ -0,0 +1,72 @@
<template>
<v-container grid-list-xs fluid class="pa-0 photo-mosaic">
<v-card v-if="photos.length === 0">
<v-card-title primary-title>
<div>
<h3 class="headline mb-3">No photos matched your search</h3>
<div>Try using other terms and search options such as category, country and camera.</div>
</div>
</v-card-title>
</v-card>
<v-layout row wrap>
<v-flex
v-for="(photo, index) in photos"
:key="photo.ID"
xs4 sm3 md2 lg1 d-flex
v-bind:class="{ selected: photo.selected }" class="single-photo"
>
<v-hover>
<v-card tile slot-scope="{ hover }"
:dark="photo.selected"
:class="photo.selected ? 'elevation-14 ma-1' : hover ? 'elevation-6 ma-2' : 'elevation-2 ma-2'">
<v-img :src="photo.getThumbnailUrl('tile_224')"
aspect-ratio="1"
class="grey lighten-2"
style="cursor: pointer"
@click="open(index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate
color="grey lighten-5"></v-progress-circular>
</v-layout>
<v-btn v-if="hover || photo.selected" :flat="!hover" icon large absolute
:ripple="false" style="right: 1px; bottom: 1px;"
@click.stop.prevent="select(photo)">
<v-icon v-if="photo.selected" color="white">check_box</v-icon>
<v-icon v-else color="white">check_box_outline_blank</v-icon>
</v-btn>
<v-btn v-if="hover || photo.PhotoFavorite" :flat="!hover" icon large absolute
:ripple="false" style="bottom: 1px; left: 1px"
@click.stop.prevent="like(photo)">
<v-icon v-if="photo.PhotoFavorite" color="white">favorite</v-icon>
<v-icon v-else color="white">favorite_border</v-icon>
</v-btn>
</v-img>
</v-card>
</v-hover>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
name: 'app-photo-mosaic',
props: {
photos: Array,
open: Function,
select: Function,
like: Function,
},
methods: {
}
};
</script>

View file

@ -0,0 +1,72 @@
<template>
<v-container grid-list-xs fluid class="pa-0">
<v-card v-if="photos.length === 0">
<v-card-title primary-title>
<div>
<h3 class="headline mb-3">No photos matched your search</h3>
<div>Try using other terms and search options such as category, country and camera.</div>
</div>
</v-card-title>
</v-card>
<v-layout row wrap>
<v-flex
v-for="(photo, index) in photos"
:key="photo.ID"
xs12 sm6 md3 lg2 d-flex
v-bind:class="{ selected: photo.selected }"
>
<v-hover>
<v-card tile slot-scope="{ hover }"
:dark="photo.selected"
:class="photo.selected ? 'elevation-14 ma-1' : hover ? 'elevation-6 ma-2' : 'elevation-2 ma-2'">
<v-img :src="photo.getThumbnailUrl('tile_500')"
aspect-ratio="1"
class="grey lighten-2"
style="cursor: pointer"
@click="open(index)"
>
<v-layout
slot="placeholder"
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate
color="grey lighten-5"></v-progress-circular>
</v-layout>
<v-btn v-if="hover || photo.selected" :flat="!hover" icon large absolute
:ripple="false" style="right: 4px; bottom: 4px;"
@click.stop.prevent="select(photo)">
<v-icon v-if="photo.selected" color="white">check_box</v-icon>
<v-icon v-else color="white">check_box_outline_blank</v-icon>
</v-btn>
<v-btn v-if="hover || photo.PhotoFavorite" :flat="!hover" icon large absolute
:ripple="false" style="bottom: 4px; left: 4px"
@click.stop.prevent="like(photo)">
<v-icon v-if="photo.PhotoFavorite" color="white">favorite</v-icon>
<v-icon v-else color="white">favorite_border</v-icon>
</v-btn>
</v-img>
</v-card>
</v-hover>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
name: 'app-photo-tiles',
props: {
photos: Array,
open: Function,
select: Function,
like: Function,
},
methods: {
}
};
</script>

View file

@ -2,6 +2,10 @@ import AppAlert from "./app-alert.vue";
import AppNavigation from "./app-navigation.vue"; import AppNavigation from "./app-navigation.vue";
import AppLoadingBar from "./app-loading-bar.vue"; import AppLoadingBar from "./app-loading-bar.vue";
import AppGallery from "./app-gallery.vue"; import AppGallery from "./app-gallery.vue";
import AppPhotoDetails from "./app-photo-details.vue";
import AppPhotoTiles from "./app-photo-tiles.vue";
import AppPhotoMosaic from "./app-photo-mosaic.vue";
import AppPhotoList from "./app-photo-list.vue";
const components = {}; const components = {};
@ -10,6 +14,10 @@ components.install = (Vue) => {
Vue.component("app-gallery", AppGallery); Vue.component("app-gallery", AppGallery);
Vue.component("app-navigation", AppNavigation); Vue.component("app-navigation", AppNavigation);
Vue.component("app-loading-bar", AppLoadingBar); Vue.component("app-loading-bar", AppLoadingBar);
Vue.component("app-photo-details", AppPhotoDetails);
Vue.component("app-photo-tiles", AppPhotoTiles);
Vue.component("app-photo-mosaic", AppPhotoMosaic);
Vue.component("app-photo-list", AppPhotoList);
}; };
export default components; export default components;

View file

@ -88,7 +88,6 @@ const config = {
hmr: false, hmr: false,
fallback: "vue-style-loader", fallback: "vue-style-loader",
use: [ use: [
// "vue-style-loader",
"style-loader", "style-loader",
{ {
loader: "css-loader", loader: "css-loader",