Files
Pinry/pinry-spa/src/components/Pins.vue
2019-12-08 12:58:50 -05:00

378 lines
9.1 KiB
Vue

<template>
<div class="pins">
<section class="section">
<div id="pins-container" class="container" v-if="blocks">
<div
v-masonry=""
transition-duration="0.3s"
item-selector=".grid-item"
column-width=".grid-sizer"
gutter=".gutter-sizer"
>
<template v-for="item in blocks">
<div v-bind:key="item.id"
v-masonry-tile
:class="item.class"
class="grid pin-masonry">
<div class="grid-sizer"></div>
<div class="gutter-sizer"></div>
<div class="pin-card grid-item">
<img :src="item.url"
@load="onPinImageLoaded(item.id)"
@click="openPreview(item)"
alt="item.description"
:style="item.style"
class="pin-preview-image">
<div class="pin-footer">
<div class="description" v-show="item.description"><p>{{ item.description }}</p>
</div>
<div class="details">
<div class="is-pulled-left">
<img class="avatar" :src="item.avatar" alt="">
</div>
<div class="pin-info">
<span class="dim">pined by&nbsp;
<span>
<router-link
:to="{ name: 'user', params: {user: item.author} }">
{{ item.author }}
</router-link>
</span>
<template v-if="item.tags.length > 0">
&nbsp;in&nbsp;
<template v-for="tag in item.tags">
<span v-bind:key="tag" class="pin-tag">
<router-link :to="{ name: 'tag', params: {tag: tag} }"
params="{tag: tag}">{{ tag }}</router-link>
</span>
</template>
</template>
</span>
</div>
<div class="is-clearfix"></div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
<loadingSpinner v-bind:show="status.loading"></loadingSpinner>
<noMore v-bind:show="!status.hasNext"></noMore>
</section>
</div>
</template>
<script>
import API from './api';
import pinHandler from './utils/PinHandler';
import PinPreview from './PinPreview.vue';
import loadingSpinner from './loadingSpinner.vue';
import noMore from './noMore.vue';
import scroll from './utils/scroll';
function createImageItem(pin) {
const image = {};
image.url = pinHandler.escapeUrl(pin.image.thumbnail.image);
image.id = pin.id;
image.owner_id = pin.submitter.id;
image.description = pin.description;
image.tags = pin.tags;
image.author = pin.submitter.username;
image.avatar = `//gravatar.com/avatar/${pin.submitter.gravatar}`;
image.large_image_url = pinHandler.escapeUrl(pin.image.image);
image.original_image_url = pin.url;
image.referer = pin.referer;
image.orgianl_width = pin.image.width;
image.style = {
width: `${pin.image.thumbnail.width}px`,
height: `${pin.image.thumbnail.height}px`,
};
image.class = {};
return image;
}
export default {
name: 'pins',
components: {
loadingSpinner,
noMore,
},
data() {
return {
blocks: [],
blocksMap: {},
status: {
loading: false,
hasNext: true,
offset: 0,
},
};
},
props: {
pinFilters: {
type: Object,
default() {
return {
tagFilter: null,
userFilter: null,
};
},
},
},
methods: {
onPinImageLoaded(itemId) {
this.blocksMap[itemId].class = {
'image-loaded': true,
};
},
registerScrollEvent() {
const self = this;
scroll.bindScroll2Bottom(
() => {
if (self.status.loading || !self.status.hasNext) {
return;
}
self.fetchMore();
},
);
},
buildBlocks(results) {
const blocks = [];
results.forEach(
(pin) => {
const item = createImageItem(pin);
blocks.push(
item,
);
},
);
return blocks;
},
openPreview(pinItem) {
this.$buefy.modal.open(
{
parent: this,
component: PinPreview,
props: {
pinItem,
},
scroll: 'keep',
customClass: 'pin-preview-at-home',
},
);
},
shouldFetchMore(created) {
if (!created) {
if (this.status.loading) {
return false;
}
if (!this.status.hasNext) {
return false;
}
}
return true;
},
fetchMore(created) {
if (!this.shouldFetchMore(created)) {
return;
}
this.status.loading = true;
let promise;
if (this.pinFilters.tagFilter) {
promise = API.fetchPins(this.status.offset, this.pinFilters.tagFilter);
} else if (this.pinFilters.userFilter) {
promise = API.fetchPins(this.status.offset, null, this.pinFilters.userFilter);
} else if (this.pinFilters.boardFilter) {
promise = API.fetchPinsForBoard(this.pinFilters.boardFilter);
} else if (this.pinFilters.idFilter) {
promise = API.fetchPin(this.pinFilters.idFilter);
} else {
promise = API.fetchPins(this.status.offset);
}
promise.then(
(resp) => {
const { results, next } = resp.data;
let newBlocks = this.buildBlocks(results);
newBlocks.forEach(
(item) => { this.blocksMap[item.id] = item; },
);
newBlocks = this.blocks.concat(newBlocks);
this.blocks = newBlocks;
this.status.offset = newBlocks.length;
this.status.hasNext = !(next === null);
this.status.loading = false;
},
() => { this.status.loading = false; },
);
},
},
created() {
this.registerScrollEvent();
this.fetchMore(true);
},
};
</script>
<style lang="scss" scoped>
/* grid */
.grid-sizer,
.grid-item { width: 240px; }
.grid-item {
margin-bottom: 15px;
}
.gutter-sizer {
width: 15px;
}
/* pin-image transition */
.pin-masonry.image-loaded{
opacity: 1;
transition: opacity .3s;
}
.pin-masonry {
opacity: 0;
}
/* card */
$pin-footer-position-fix: -6px;
$avatar-width: 30px;
$avatar-height: 30px;
@mixin pin-detail-font-size{
font-size: 12px;
}
@mixin description-font {
@include pin-detail-font-size;
font-family: 'Georgia', 'Times', 'Times New Roman', serif;
}
@mixin secondary-font {
@include pin-detail-font-size;
color: #999;
font-weight: normal;
}
@mixin loader {
background: url('../assets/loader.gif');
background-position: center center;
background-repeat: no-repeat;
}
.pin-card{
.pin-preview-image {
cursor: zoom-in;
}
> img {
background-color: white;
border-radius: 3px 3px 0 0;
@include loader;
}
.avatar {
height: $avatar-height;
width: $avatar-width;
border-radius: 3px;
}
.pin-tag {
margin-right: 0.2rem;
}
}
.pin-footer {
position: relative;
top: $pin-footer-position-fix;
background-color: white;
border-radius: 0 0 3px 3px ;
box-shadow: 0 1px 0 #bbb;
.description {
@include description-font;
padding: 8px;
border-bottom: 1px solid #DDDDDD;
overflow: hidden;
text-overflow: ellipsis;
}
.details {
@include secondary-font;
padding: 10px;
> .pin-info {
line-height: 16px;
width: 220px;
padding-left: $avatar-width + 5px;
}
.pin-info a {
font-weight: bold;
}
}
}
/*pin container size fit*/
/* Generated by python code:
/* def fn(x):
/* gutter = 15
/* image_width = 240
/* border_width_of_parent = 24
/* size = image_width * x + gutter * (x - 1)
/* width = size + border_width_of_parent * 2
/* print(template % (width, size))
/*
/* for i in range(1, 12):
/* fn(i)
*/
@media screen and (min-width: 288px) {
#pins-container {
max-width: 240px;
}
}
@media screen and (min-width: 543px) {
#pins-container {
max-width: 495px;
}
}
@media screen and (min-width: 798px) {
#pins-container {
max-width: 750px;
}
}
@media screen and (min-width: 1053px) {
#pins-container {
max-width: 1005px;
}
}
@media screen and (min-width: 1308px) {
#pins-container {
max-width: 1260px;
}
}
@media screen and (min-width: 1563px) {
#pins-container {
max-width: 1515px;
}
}
@media screen and (min-width: 1818px) {
#pins-container {
max-width: 1770px;
}
}
@media screen and (min-width: 2073px) {
#pins-container {
max-width: 2025px;
}
}
@media screen and (min-width: 2328px) {
#pins-container {
max-width: 2280px;
}
}
@media screen and (min-width: 2583px) {
#pins-container {
max-width: 2535px;
}
}
@media screen and (min-width: 2838px) {
#pins-container {
max-width: 2790px;
}
}
</style>