Add Carousel and MovieCard components

This commit is contained in:
2020-10-04 23:39:24 +02:00
parent a195ddd757
commit 13d19906dd
8 changed files with 270 additions and 106 deletions

View File

@@ -0,0 +1,49 @@
<template>
<carousel
:perPageCustom="[
[320, 1],
[480, 2],
[768, 3]
]"
:loop="true"
:navigationEnabled="true"
:paginationEnabled="false"
:navigationPrevLabel="arrowLeft"
:navigationNextLabel="arrowRight"
>
<slide v-for="movie in dataSource" :key="movie.imdbID">
<MovieCard :loading="loading" :movie="movie" />
</slide>
</carousel>
</template>
<script>
import { Carousel, Slide } from "vue-carousel";
import { mapState } from "vuex";
import { Icon } from "ant-design-vue";
export default {
components: { Carousel, Slide, Icon },
props: {
dataSource: {
type: Array,
default: []
}
},
computed: {
...mapState({
loading: "loading"
})
},
data() {
return {
arrowLeft:
'<svg viewBox="64 64 896 896" data-icon="arrow-left" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"></path></svg>',
arrowRight:
'<svg viewBox="64 64 896 896" data-icon="arrow-right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91.5c1.9 0 3.8-.7 5.2-2L869 536.2a32.07 32.07 0 0 0 0-48.4z"></path></svg>'
};
}
};
</script>
<style></style>

View File

@@ -0,0 +1,62 @@
<template>
<a-card :loading="loading" :bordered="false">
<a-spin class="spinner" v-if="loading" slot="cover" />
<img
v-else
slot="cover"
:alt="movie.Title"
:src="
movie.Poster == 'N/A'
? 'https://placeimg.com/200/200/any?1'
: movie.Poster
"
/>
<a-card-meta :title="movie.Title">
<template slot="description">
{{ movie.Director }} -
<span style="font-size: 12px; font-style: oblique;">{{
movie.Year
}}</span>
</template>
</a-card-meta>
</a-card>
</template>
<script>
export default {
props: {
movie: {
type: Object,
default: () => {}
},
loading: {
type: Boolean,
default: true
}
}
};
</script>
<style>
.spinner {
text-align: center;
background: rgba(207, 216, 220, 0.4);
border-radius: 4px 4px 0 0;
margin-bottom: 20px;
padding: 30px 50px;
}
.ant-card {
height: 100%;
}
.ant-card-cover img {
max-height: 256px;
object-fit: scale-down;
}
.ant-card-cover {
background-color: rgb(13, 10, 57);
}
</style>

View File

@@ -42,4 +42,19 @@ h2 {
height: 48px;
margin: 0 12px 0 0;
}
@media screen and (max-width: 414px) {
.NuxtLogo {
width: 28px;
height: 28px;
}
h1 {
font-size: 18px;
}
h2 {
display: none;
}
}
</style>

39
nuxt/package-lock.json generated
View File

@@ -5204,6 +5204,11 @@
}
}
},
"dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@@ -6439,6 +6444,15 @@
"is-glob": "^4.0.1"
}
},
"global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
"requires": {
"min-document": "^2.19.0",
"process": "^0.11.10"
}
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -9386,6 +9400,14 @@
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ=="
},
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"requires": {
"dom-walk": "^0.1.0"
}
},
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -13986,6 +14008,23 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
},
"vue-carousel": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/vue-carousel/-/vue-carousel-0.18.0.tgz",
"integrity": "sha512-a2zxh7QJioDxNMguqcuJ7TPbfgK5bGDaAXIia7NWxPAWsEvNE4ZtHgsGu40L5Aha4uyjmNKXvleB14QAXFoKig==",
"requires": {
"global": "^4.3.2",
"regenerator-runtime": "^0.12.1",
"vue": "^2.5.17"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"vue-client-only": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-client-only/-/vue-client-only-2.0.0.tgz",

View File

@@ -16,7 +16,8 @@
"ant-design-vue": "^1.6.5",
"core-js": "^3.6.5",
"lodash": "^4.17.20",
"nuxt": "^2.14.5"
"nuxt": "^2.14.5",
"vue-carousel": "^0.18.0"
},
"devDependencies": {
"@vue/test-utils": "^1.1.0",

View File

@@ -3,7 +3,7 @@
<TheHeader />
<a-layout-content>
<a-row type="flex" align="middle" justify="center" style="height: 100%;">
<a-col :span="12">
<a-col :xs="18" :md="12" :lg="8">
<SearchInput />
</a-col>
</a-row>

View File

@@ -3,54 +3,16 @@
<TheHeader />
<a-layout-content>
<a-row type="flex" align="middle" justify="center" style="height: 100%;">
<a-col :span="12">
<nuxt-link to="/">
<a-button type="primary"> <a-icon type="left" />Go back </a-button>
</nuxt-link>
<h3 style="color: #FFFFFF;">
Your search: "{{ search }}" ({{ nbResults }} match)
</h3>
<a-carousel arrows dot-position="top">
<div
slot="prevArrow"
class="custom-slick-arrow"
style="left: 10px;zIndex: 1"
<a-col :xs="20" :md="18">
<a-page-header
:ghost="false"
:title="`Your search : &quot;${search}&quot;`"
:sub-title="`${nbResults} match`"
@back="() => $router.push('/')"
>
<a-icon type="left-circle" />
</div>
<div
slot="nextArrow"
class="custom-slick-arrow"
style="right: 10px"
>
<a-icon type="right-circle" />
</div>
<a-card
:bordered="false"
:key="movie.imdbID"
v-for="movie in movies"
>
<img
slot="cover"
:alt="movie.Title"
:src="
movie.Poster === 'N/A'
? 'https://place-hold.it/285x380'
: movie.Poster
"
/>
<a-card-meta :title="movie.Title">
<template slot="description">
{{ movie.Director }}
-
<span style="font-size: 12px; font-style: oblique;">{{
movie.Year
}}</span>
</template>
</a-card-meta>
</a-card>
</a-carousel>
<Carousel v-if="!error" :dataSource="movies" />
<a-alert v-else :message="error" type="error" show-icon />
</a-page-header>
</a-col>
</a-row>
</a-layout-content>
@@ -65,19 +27,22 @@ export default {
middleware: "redirect",
async asyncData({ store }) {
await store.dispatch("getMovies");
await store.dispatch("setDirectors");
},
computed: {
...mapState({
search: "search",
movies: "movies",
nbResults: "nbResults",
error: "error"
error: "error",
loading: "loading"
})
}
};
</script>
<style scoped>
<style>
#main {
height: 100vh;
width: 100%;
@@ -88,28 +53,49 @@ export default {
background-image: url("~assets/background.jpg");
}
.ant-carousel >>> .slick-slide {
.VueCarousel-navigation-prev {
left: -8px;
}
.VueCarousel-navigation-next {
right: -8px;
}
.ant-page-header {
background-color: #f5f5f5;
padding: 24px 44px;
}
.ant-page-header-content {
overflow: unset;
}
.VueCarousel-slide {
max-width: 100%;
}
@media screen and (min-width: 768px) {
.VueCarousel-slide {
max-width: 50%;
}
}
@media screen and (min-width: 992px) {
.VueCarousel-slide {
max-width: 33.333333%;
}
}
@media screen and (min-width: 1200px) {
.VueCarousel-slide {
max-width: 25%;
}
}
@media screen and (max-width: 414px) {
.ant-page-header-heading-sub-title {
width: 100%;
text-align: center;
overflow: hidden;
}
.ant-carousel >>> .slick-slide .ant-card {
width: 285px !important;
}
.ant-carousel >>> .custom-slick-arrow {
width: 25px;
height: 25px;
font-size: 25px;
color: #fff;
background-color: rgba(31, 45, 61, 0.11);
opacity: 0.3;
}
.ant-carousel >>> .custom-slick-arrow:before {
display: none;
}
.ant-carousel >>> .custom-slick-arrow:hover {
opacity: 0.5;
}
</style>

View File

@@ -5,7 +5,7 @@ export const state = () => ({
movies: [],
nbResults: 0,
loading: false,
error: ""
error: null
});
export const mutations = {
@@ -18,50 +18,62 @@ export const mutations = {
};
export const actions = {
async getMovies({ commit, state, dispatch }) {
async getMovies({ commit, state }) {
commit("setLoading", true);
const response = await this.$axios.$get("", {
return await this.$axios
.$get("", {
params: {
apikey: "a4bf96a7",
s: state.search,
type: "movie"
}
});
})
.then(response => {
if (response.Response === "False") throw new Error(response.Error);
if (response.Response === "True") {
commit("setNbResults", response.totalResults);
dispatch("setDirectors", response.Search).then(function(res) {
console.log(res);
commit("setMovies", res);
});
} else if (response.Response === "False") {
commit("setMovies", response.Search);
commit("setError", null);
})
.catch(e => {
commit("setMovies", []);
commit("setNbResults", 0);
commit("setError", response.Error);
}
commit("setError", e.message);
})
.finally(() => {
commit("setLoading", false);
});
},
async getDirector({}, id) {
const response = await this.$axios.$get("", {
return await this.$axios
.$get("", {
params: {
apikey: "a4bf96a7",
i: id,
type: "movie"
}
});
})
.then(response => {
if (response.Response === "False") throw new Error(response.Error);
if (response.Response === "True") return response.Director;
else if (response.Response === "False") throw new Error(response.Error);
return response.Director;
})
.catch(e => commit("setError", e.message));
},
setDirectors({ dispatch }, data) {
setDirectors({ state, dispatch, commit }) {
commit("setLoading", true);
let tmp = [];
data.forEach(async (movie, index) => {
const director = await dispatch("getDirector", movie.imdbID);
state.movies.forEach(async movie => {
return await dispatch("getDirector", movie.imdbID).then(director => {
movie.Director = director;
tmp.push(movie);
});
return tmp;
});
commit("setMovies", tmp);
commit("setLoading", false);
}
};