Merge pull request #2317 from Human-Connection/2253-fix-scroll-layout-issue

2253 fix scroll layout issue
This commit is contained in:
mattwr18 2019-12-05 21:02:10 +01:00 committed by GitHub
commit 9fd4ad47dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 219 additions and 56 deletions

View File

@ -39,4 +39,5 @@ module.exports = {
default: () => new Date().toISOString(),
},
language: { type: 'string', allow: [null] },
imageAspectRatio: { type: 'float', default: 1.0 },
}

View File

@ -309,7 +309,15 @@ export default {
},
Post: {
...Resolver('Post', {
undefinedToNull: ['activityId', 'objectId', 'image', 'language', 'pinnedAt', 'pinned'],
undefinedToNull: [
'activityId',
'objectId',
'image',
'language',
'pinnedAt',
'pinned',
'imageAspectRatio',
],
hasMany: {
tags: '-[:TAGGED]->(related:Tag)',
categories: '-[:CATEGORIZED]->(related:Category)',

View File

@ -119,6 +119,7 @@ type Post {
contentExcerpt: String
image: String
imageUpload: Upload
imageAspectRatio: Float
visibility: Visibility
deleted: Boolean
disabled: Boolean
@ -182,6 +183,7 @@ type Mutation {
language: String
categoryIds: [ID]
contentExcerpt: String
imageAspectRatio: Float
): Post
UpdatePost(
id: ID!
@ -194,6 +196,7 @@ type Mutation {
visibility: Visibility
language: String
categoryIds: [ID]
imageAspectRatio: Float
): Post
DeletePost(id: ID!): Post
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
@ -218,6 +221,7 @@ type Query {
offset: Int
orderBy: [_PostOrdering]
filter: _PostFilter
imageAspectRatio: Float
): [Post]
PostsEmotionsCountByEmotion(postId: ID!, data: _EMOTEDInput!): Int!
PostsEmotionsByCurrentUser(postId: ID!): [String]

View File

@ -19,6 +19,7 @@ export default function create() {
visibility: 'public',
deleted: false,
categoryIds: [],
imageAspectRatio: 1.333,
}
args = {
...defaults,

View File

@ -350,15 +350,17 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
author: peterLustig,
id: 'p0',
language: sample(languages),
image: faker.image.unsplash.food(),
image: faker.image.unsplash.food(300, 169),
categoryIds: ['cat16'],
imageAspectRatio: 300 / 169,
}),
factory.create('Post', {
author: bobDerBaumeister,
id: 'p1',
language: sample(languages),
image: faker.image.unsplash.technology(),
image: faker.image.unsplash.technology(300, 1500),
categoryIds: ['cat1'],
imageAspectRatio: 300 / 1500,
}),
factory.create('Post', {
author: huey,
@ -382,8 +384,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
authorId: 'u1',
id: 'p6',
language: sample(languages),
image: faker.image.unsplash.buildings(),
image: faker.image.unsplash.buildings(300, 857),
categoryIds: ['cat6'],
imageAspectRatio: 300 / 857,
}),
factory.create('Post', {
author: huey,
@ -400,8 +403,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
author: louie,
id: 'p11',
language: sample(languages),
image: faker.image.unsplash.people(),
image: faker.image.unsplash.people(300, 901),
categoryIds: ['cat11'],
imageAspectRatio: 300 / 901,
}),
factory.create('Post', {
author: bobDerBaumeister,
@ -413,8 +417,9 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
author: jennyRostock,
id: 'p14',
language: sample(languages),
image: faker.image.unsplash.objects(),
image: faker.image.unsplash.objects(300, 200),
categoryIds: ['cat14'],
imageAspectRatio: 300 / 450,
}),
factory.create('Post', {
author: huey,
@ -434,8 +439,20 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
const hashtagAndMention1 =
'The new physics of <a class="hashtag" data-hashtag-id="QuantenFlussTheorie" href="/?hashtag=QuantenFlussTheorie">#QuantenFlussTheorie</a> can explain <a class="hashtag" data-hashtag-id="QuantumGravity" href="/?hashtag=QuantumGravity">#QuantumGravity</a>! <a class="mention" data-mention-id="u1" href="/profile/u1">@peter-lustig</a> got that already. ;-)'
const createPostMutation = gql`
mutation($id: ID, $title: String!, $content: String!, $categoryIds: [ID]) {
CreatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds) {
mutation(
$id: ID
$title: String!
$content: String!
$categoryIds: [ID]
$imageAspectRatio: Float
) {
CreatePost(
id: $id
title: $title
content: $content
categoryIds: $categoryIds
imageAspectRatio: $imageAspectRatio
) {
id
}
}
@ -449,6 +466,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
title: `Nature Philosophy Yoga`,
content: hashtag1,
categoryIds: ['cat2'],
imageAspectRatio: 300 / 200,
},
}),
mutate({
@ -458,6 +476,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
title: 'This is post #7',
content: `${mention1} ${faker.lorem.paragraph()}`,
categoryIds: ['cat7'],
imageAspectRatio: 300 / 180,
},
}),
mutate({
@ -468,6 +487,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
title: `Quantum Flow Theory explains Quantum Gravity`,
content: hashtagAndMention1,
categoryIds: ['cat8'],
imageAspectRatio: 300 / 900,
},
}),
mutate({
@ -477,6 +497,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
title: 'This is post #12',
content: `${mention2} ${faker.lorem.paragraph()}`,
categoryIds: ['cat12'],
imageAspectRatio: 300 / 200,
},
}),
])

View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
echo "Database manipulation is not possible without connecting to the database."
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
fi
until echo 'RETURN "Connection successful" as info;' | cypher-shell
do
echo "Connecting to neo4j failed, trying again..."
sleep 1
done
shopt -s nullglob
for image in uploads/*; do
[ -e "$image" ] || continue
IMAGE_WIDTH=$( identify -format '%w' "$image" )
IMAGE_HEIGHT=$( identify -format '%h' "$image" )
IMAGE_ASPECT_RATIO=$(echo | awk "{ print ${IMAGE_WIDTH}/${IMAGE_HEIGHT}}")
echo "$image"
echo "$IMAGE_ASPECT_RATIO"
echo "
match (post:Post {image: '/"${image}"'})
set post.imageAspectRatio = "${IMAGE_ASPECT_RATIO}"
return post;
" | cypher-shell
done

View File

@ -198,6 +198,7 @@ describe('ContributionForm.vue', () => {
id: null,
categoryIds: ['cat12'],
imageUpload: null,
imageAspectRatio: null,
image: null,
},
}
@ -352,6 +353,7 @@ describe('ContributionForm.vue', () => {
categoryIds: ['cat12'],
image,
imageUpload: null,
imageAspectRatio: null,
},
}
})

View File

@ -7,7 +7,11 @@
@submit="submit"
>
<template slot-scope="{ errors }">
<hc-teaser-image :contribution="contribution" @addTeaserImage="addTeaserImage">
<hc-teaser-image
:contribution="contribution"
@addTeaserImage="addTeaserImage"
@addImageAspectRatio="addImageAspectRatio"
>
<img
v-if="contribution"
class="contribution-image"
@ -128,6 +132,7 @@ export default {
title: '',
content: '',
teaserImage: null,
imageAspectRatio: null,
image: null,
language: null,
categoryIds: [],
@ -190,6 +195,7 @@ export default {
content,
image,
teaserImage,
imageAspectRatio,
categoryIds,
} = this.form
this.loading = true
@ -204,6 +210,7 @@ export default {
language,
image,
imageUpload: teaserImage,
imageAspectRatio,
},
})
.then(({ data }) => {
@ -227,6 +234,9 @@ export default {
addTeaserImage(file) {
this.form.teaserImage = file
},
addImageAspectRatio(aspectRatio) {
this.form.imageAspectRatio = aspectRatio
},
categoryIds(categories) {
return categories.map(c => c.id)
},

View File

@ -6,23 +6,36 @@ const localVue = global.localVue
describe('MasonryGrid', () => {
let wrapper
let masonryGrid
let masonryGridItem
beforeEach(() => {
wrapper = mount(MasonryGrid, { localVue })
masonryGrid = wrapper.vm.$children[0]
masonryGridItem = wrapper.vm.$children[0]
})
it('adds the "reset-grid-height" class when one or more children are updating', () => {
masonryGrid.$emit('calculating-item-height')
it('adds the "reset-grid-height" class when itemsCalculating is more than 0', () => {
wrapper.setData({ itemsCalculating: 1 })
expect(wrapper.classes()).toContain('reset-grid-height')
})
it('removes the "reset-grid-height" class when all children have completed updating', () => {
wrapper.setData({ itemsCalculating: 1 })
masonryGrid.$emit('finished-calculating-item-height')
it('removes the "reset-grid-height" class when itemsCalculating is 0', () => {
wrapper.setData({ itemsCalculating: 0 })
expect(wrapper.classes()).not.toContain('reset-grid-height')
})
it('adds 1 to itemsCalculating when a child emits "calculating-item-height"', () => {
wrapper.setData({ itemsCalculating: 0 })
masonryGridItem.$emit('calculating-item-height')
expect(wrapper.vm.itemsCalculating).toBe(1)
})
it('subtracts 1 from itemsCalculating when a child emits "finished-calculating-item-height"', () => {
wrapper.setData({ itemsCalculating: 2 })
masonryGridItem.$emit('finished-calculating-item-height')
expect(wrapper.vm.itemsCalculating).toBe(1)
})
})

View File

@ -1,6 +1,5 @@
<template>
<ds-grid
:min-column-width="300"
v-on:calculating-item-height="startCalculation"
v-on:finished-calculating-item-height="endCalculation"
:class="[itemsCalculating ? 'reset-grid-height' : '']"
@ -27,7 +26,14 @@ export default {
}
</script>
<style>
<style lang="scss">
/* dirty fix to override broken styleguide inline-styles */
.ds-grid {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)) !important;
gap: 16px !important;
grid-auto-rows: 20px;
}
.reset-grid-height {
grid-auto-rows: auto !important;
align-items: self-start;

View File

@ -1,5 +1,4 @@
import { config, shallowMount } from '@vue/test-utils'
import { config, mount } from '@vue/test-utils'
import MasonryGridItem from './MasonryGridItem'
const localVue = global.localVue
@ -9,24 +8,41 @@ config.stubs['ds-grid-item'] = '<span><slot /></span>'
describe('MasonryGridItem', () => {
let wrapper
beforeEach(() => {
wrapper = shallowMount(MasonryGridItem, { localVue })
wrapper.vm.$parent.$emit = jest.fn()
describe('given an imageAspectRatio', () => {
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => {
const propsData = { imageAspectRatio: 2 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(13)
})
it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => {
const propsData = { imageAspectRatio: 1.1 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(15)
})
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => {
const propsData = { imageAspectRatio: 0.7 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(18)
})
it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => {
const propsData = { imageAspectRatio: 0.3 }
wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(25)
})
})
it('emits "calculating-item-height" when starting calculation', async () => {
wrapper.vm.calculateItemHeight()
await wrapper.vm.$nextTick()
describe('given no aspect ratio', () => {
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => {
wrapper = mount(MasonryGridItem, { localVue })
const firstCallArgument = wrapper.vm.$parent.$emit.mock.calls[0][0]
expect(firstCallArgument).toBe('calculating-item-height')
})
it('emits "finished-calculating-item-height" after the calculation', async () => {
wrapper.vm.calculateItemHeight()
await wrapper.vm.$nextTick()
const secondCallArgument = wrapper.vm.$parent.$emit.mock.calls[1][0]
expect(secondCallArgument).toBe('finished-calculating-item-height')
expect(wrapper.vm.rowSpan).toBe(8)
})
})
})

View File

@ -5,15 +5,33 @@
</template>
<script>
const landscapeRatio = 1.3
const squareRatio = 1
const portraitRatio = 0.7
const getRowSpan = aspectRatio => {
if (aspectRatio >= landscapeRatio) return 13
else if (aspectRatio >= squareRatio) return 15
else if (aspectRatio >= portraitRatio) return 18
else return 25
}
export default {
props: {
imageAspectRatio: {
type: Number,
default: null,
},
},
data() {
return {
rowSpan: 10,
rowSpan: this.imageAspectRatio ? getRowSpan(this.imageAspectRatio) : 8,
}
},
methods: {
calculateItemHeight() {
this.$parent.$emit('calculating-item-height')
this.$nextTick(() => {
const gridStyle = this.$parent.$el.style
const rowHeight = parseInt(gridStyle.gridAutoRows)
@ -27,13 +45,7 @@ export default {
},
},
mounted() {
const image = this.$el.querySelector('img')
if (image) {
image.onload = () => this.calculateItemHeight()
} else {
// use timeout to make sure layout is set up before calculation
setTimeout(() => this.calculateItemHeight(), 0)
}
this.calculateItemHeight()
},
}
</script>

View File

@ -13,7 +13,7 @@
</nuxt-link>
<ds-space margin-bottom="small" />
<!-- Username, Image & Date of Post -->
<div>
<div class="user-wrapper">
<client-only>
<hc-user :user="post.author" :trunc="35" :date-time="post.createdAt" />
</client-only>
@ -141,10 +141,19 @@ export default {
this.$emit('unpinPost', post)
},
},
mounted() {
const width = this.$el.offsetWidth
const height = Math.min(width / this.post.imageAspectRatio, 2000)
const imageElement = this.$el.querySelector('.ds-card-image')
if (imageElement) {
imageElement.style.height = `${height}px`
}
},
}
</script>
<style lang="scss" scoped>
<style lang="scss">
.ds-card-image img {
width: 100%;
max-height: 2000px;
@ -159,9 +168,21 @@ export default {
cursor: pointer;
position: relative;
z-index: 1;
justify-content: space-between;
/*.ds-card-footer {
}*/
> .ds-card-content {
flex-grow: 0;
}
/* workaround to avoid jumping layout when footer is rendered */
> .ds-card-footer {
height: 75px;
}
/* workaround to avoid jumping layout when hc-user is rendered */
.user-wrapper {
height: 36px;
}
.content-menu {
display: inline-block;

View File

@ -112,10 +112,12 @@ export default {
this.showCropper = false
const canvas = this.cropper.getCroppedCanvas()
canvas.toBlob(blob => {
const imageAspectRatio = canvas.width / canvas.height
this.setupPreview(canvas)
this.removeCropper()
const croppedImageFile = new File([blob], this.file.name, { type: 'image/jpeg' })
const croppedImageFile = new File([blob], this.file.name, { type: this.file.type })
this.$emit('addTeaserImage', croppedImageFile)
this.$emit('addImageAspectRatio', imageAspectRatio)
}, 'image/jpeg')
},
setupPreview(canvas) {

View File

@ -148,7 +148,6 @@ export default {
<style scoped lang="scss">
.avatar {
display: inline-block;
float: left;
margin-right: 4px;
height: 100%;

View File

@ -64,6 +64,7 @@ export const postFragment = lang => gql`
role
}
pinnedAt
imageAspectRatio
}
`
export const commentFragment = lang => gql`

View File

@ -9,6 +9,7 @@ export default () => {
$language: String
$categoryIds: [ID]
$imageUpload: Upload
$imageAspectRatio: Float
) {
CreatePost(
title: $title
@ -16,6 +17,7 @@ export default () => {
language: $language
categoryIds: $categoryIds
imageUpload: $imageUpload
imageAspectRatio: $imageAspectRatio
) {
title
slug
@ -34,6 +36,7 @@ export default () => {
$imageUpload: Upload
$categoryIds: [ID]
$image: String
$imageAspectRatio: Float
) {
UpdatePost(
id: $id
@ -43,6 +46,7 @@ export default () => {
imageUpload: $imageUpload
categoryIds: $categoryIds
image: $image
imageAspectRatio: $imageAspectRatio
) {
id
title
@ -55,6 +59,7 @@ export default () => {
name
role
}
imageAspectRatio
}
}
`,

View File

@ -16,10 +16,13 @@
</div>
</ds-grid-item>
<template v-if="hasResults">
<masonry-grid-item v-for="post in posts" :key="post.id">
<masonry-grid-item
v-for="post in posts"
:key="post.id"
:imageAspectRatio="post.imageAspectRatio"
>
<hc-post-card
:post="post"
:width="{ base: '100%', xs: '100%', md: '50%', xl: '33%' }"
@removePostFromList="deletePost"
@pinPost="pinPost"
@unpinPost="unpinPost"

View File

@ -19,7 +19,11 @@
<h3>{{ $t('post.moreInfo.titleOfRelatedContributionsSection') }}</h3>
<ds-section>
<masonry-grid v-if="post.relatedContributions && post.relatedContributions.length">
<masonry-grid-item v-for="relatedPost in post.relatedContributions" :key="relatedPost.id">
<masonry-grid-item
v-for="relatedPost in post.relatedContributions"
:key="relatedPost.id"
:imageAspectRatio="relatedPost.imageAspectRatio"
>
<hc-post-card
:post="relatedPost"
:width="{ base: '100%', lg: 1 }"

View File

@ -115,7 +115,7 @@ describe('ProfileSlug', () => {
})
it('displays a loading spinner below the posts list', () => {
expect(wrapper.find('.user-profile-posts-list .ds-spinner').exists()).toBe(true)
expect(wrapper.find('.ds-spinner').exists()).toBe(true)
})
})
})

View File

@ -168,7 +168,7 @@
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
<masonry-grid class="user-profile-posts-list">
<masonry-grid>
<ds-grid-item class="profile-top-navigation" :row-span="3" column-span="fullWidth">
<ds-card class="ds-tab-nav">
<ul class="Tabs">
@ -232,7 +232,11 @@
</ds-grid-item>
<template v-if="posts.length">
<masonry-grid-item v-for="post in posts" :key="post.id">
<masonry-grid-item
v-for="post in posts"
:key="post.id"
:imageAspectRatio="post.imageAspectRatio"
>
<hc-post-card
:post="post"
:width="{ base: '100%', md: '100%', xl: '50%' }"