mirror of
https://github.com/pinry/pinry.git
synced 2025-11-16 18:05:51 +01:00
Feature: Add user-board view
This commit is contained in:
committed by
Isaac Bythewood
parent
c59fa32f1f
commit
e039ef7142
BIN
pinry-spa/src/assets/pinry-placeholder.jpg
Executable file
BIN
pinry-spa/src/assets/pinry-placeholder.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
227
pinry-spa/src/components/Boards.vue
Normal file
227
pinry-spa/src/components/Boards.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="boards">
|
||||
<section class="section">
|
||||
<div id="boards-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">
|
||||
<div class="grid-sizer"></div>
|
||||
<div class="gutter-sizer"></div>
|
||||
<div class="board-card grid-item">
|
||||
<router-link :to="{ name: 'board', params: { boardId: item.id } }">
|
||||
<div class="card-image">
|
||||
<img :src="item.preview_image_url"
|
||||
@load="onPinImageLoaded(item.id)"
|
||||
:style="item.style"
|
||||
v-show="item.preview_image_url"
|
||||
class="preview-image">
|
||||
</div>
|
||||
<div class="board-footer">
|
||||
<p class="sub-title board-info">{{ item.name }}</p>
|
||||
|
||||
<p class="description">
|
||||
<small>
|
||||
Pins in board: <span class="num-pins">{{ item.total_pins }}</span>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</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 loadingSpinner from './loadingSpinner.vue';
|
||||
import noMore from './noMore.vue';
|
||||
import scroll from './utils/scroll';
|
||||
import placeholder from '../assets/pinry-placeholder.jpg';
|
||||
|
||||
function createBoardItem(board) {
|
||||
const defaultPreviewImage = placeholder;
|
||||
const boardItem = {};
|
||||
const pins4Board = board.pins_detail;
|
||||
let previewImage = {
|
||||
image: { thumbnail: { image: null, width: 240, height: 240 } },
|
||||
};
|
||||
if (pins4Board.length > 0) {
|
||||
[previewImage] = pins4Board;
|
||||
}
|
||||
boardItem.id = board.id;
|
||||
boardItem.name = board.name;
|
||||
boardItem.total_pins = pins4Board.length;
|
||||
if (previewImage.image.thumbnail.image !== null) {
|
||||
boardItem.preview_image_url = pinHandler.escapeUrl(
|
||||
previewImage.image.thumbnail.image,
|
||||
);
|
||||
} else {
|
||||
boardItem.preview_image_url = defaultPreviewImage;
|
||||
}
|
||||
boardItem.style = {
|
||||
width: `${previewImage.image.thumbnail.width}px`,
|
||||
height: `${previewImage.image.thumbnail.height}px`,
|
||||
};
|
||||
boardItem.class = {};
|
||||
return boardItem;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'boards',
|
||||
components: {
|
||||
loadingSpinner,
|
||||
noMore,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
blocks: [],
|
||||
blocksMap: {},
|
||||
status: {
|
||||
loading: false,
|
||||
hasNext: true,
|
||||
offset: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
props: ['boardUsername'],
|
||||
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 = createBoardItem(pin);
|
||||
blocks.push(
|
||||
item,
|
||||
);
|
||||
},
|
||||
);
|
||||
return blocks;
|
||||
},
|
||||
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;
|
||||
const promise = API.fetchBoardForUser(this.boardUsername);
|
||||
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;
|
||||
}
|
||||
|
||||
/* card */
|
||||
$pin-footer-position-fix: -6px;
|
||||
$avatar-width: 30px;
|
||||
$avatar-height: 30px;
|
||||
@import './utils/fonts';
|
||||
@import './utils/loader.scss';
|
||||
|
||||
.board-card{
|
||||
.preview-image {
|
||||
max-height: 200px;
|
||||
}
|
||||
.card-image > img {
|
||||
background-color: white;
|
||||
border-radius: 3px 3px 0 0;
|
||||
@include loader('../assets/loader.gif');
|
||||
}
|
||||
}
|
||||
.board-footer {
|
||||
position: relative;
|
||||
top: $pin-footer-position-fix;
|
||||
background-color: white;
|
||||
border-radius: 0 0 3px 3px ;
|
||||
box-shadow: 0 1px 0 #bbb;
|
||||
font-weight: bold;
|
||||
.description {
|
||||
@include secondary-font;
|
||||
padding-left: 10px;
|
||||
padding-bottom: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.board-info {
|
||||
padding: 10px;
|
||||
color: $main-title-font-color;
|
||||
}
|
||||
.num-pins {
|
||||
font-size: 0.8rem;
|
||||
color: $main-title-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
@import 'utils/grid-layout';
|
||||
@include screen-grid-layout("#boards-container")
|
||||
|
||||
</style>
|
||||
@@ -31,7 +31,7 @@
|
||||
</a>
|
||||
<div class="navbar-dropdown">
|
||||
<router-link
|
||||
to="/boards"
|
||||
:to="{ name: 'boards4user', params: {username: user.meta.username} }"
|
||||
class="navbar-item">
|
||||
Boards
|
||||
</router-link>
|
||||
|
||||
@@ -55,6 +55,11 @@ function fetchPinsForBoard(boardId) {
|
||||
);
|
||||
}
|
||||
|
||||
function fetchBoardForUser(username) {
|
||||
const url = `${API_PREFIX}boards/?submitter__username=${username}`;
|
||||
return axios.get(url);
|
||||
}
|
||||
|
||||
const User = {
|
||||
storageKey: 'pinry.user',
|
||||
logIn(username, password) {
|
||||
@@ -128,5 +133,6 @@ export default {
|
||||
fetchPin,
|
||||
fetchPins,
|
||||
fetchPinsForBoard,
|
||||
fetchBoardForUser,
|
||||
User,
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@mixin secondary-font-color-in-dark {
|
||||
color: #878787;
|
||||
}
|
||||
$main-title-font-color: rgb(51, 51, 51);
|
||||
|
||||
// for pins component
|
||||
@mixin pin-detail-font-size{
|
||||
|
||||
@@ -5,6 +5,7 @@ import Pins4Tag from '../views/Pins4Tag.vue';
|
||||
import Pins4User from '../views/Pins4User.vue';
|
||||
import Pins4Board from '../views/Pins4Board.vue';
|
||||
import Pins4Id from '../views/Pins4Id.vue';
|
||||
import Boards4User from '../views/Boards4User.vue';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -34,6 +35,11 @@ const routes = [
|
||||
name: 'pin',
|
||||
component: Pins4Id,
|
||||
},
|
||||
{
|
||||
path: '/boards/users/:username',
|
||||
name: 'boards4user',
|
||||
component: Boards4User,
|
||||
},
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
|
||||
40
pinry-spa/src/views/Boards4User.vue
Normal file
40
pinry-spa/src/views/Boards4User.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="boards-for-user">
|
||||
<PHeader></PHeader>
|
||||
<Boards :boardUsername="username"></Boards>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PHeader from '../components/PHeader.vue';
|
||||
import Boards from '../components/Boards.vue';
|
||||
|
||||
export default {
|
||||
name: 'Boards4User',
|
||||
data() {
|
||||
return {
|
||||
username: null,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
PHeader,
|
||||
Boards,
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
this.initialize();
|
||||
next();
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.username = this.$route.params.username;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
Reference in New Issue
Block a user