Skip to content
This repository was archived by the owner on Jul 13, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/components/Beamer/BeamerIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import BeamerIcon from '../../src/components/Beamer/BeamerIcon.vue'
export default BeamerIcon
2 changes: 2 additions & 0 deletions lib/components/Beamer/BeamerImageView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import BeamerImageView from '../../src/components/Beamer/BeamerImageView.vue'
export default BeamerImageView
2 changes: 2 additions & 0 deletions lib/components/Beamer/BeamerMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import BeamerMessage from '../../src/components/Beamer/BeamerMessage.vue'
export default BeamerMessage
2 changes: 2 additions & 0 deletions lib/components/Beamer/BeamerSidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import BeamerSidebar from '../../src/components/Beamer/BeamerSidebar.vue'
export default BeamerSidebar
31 changes: 31 additions & 0 deletions src/components/Beamer/BeamerIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<a @click="openBeamer" class="header-item icon m-hide" hide-readonly="hide-readonly" v-if="!minimal">
<i class="mdi mdi-gift-outline mdi-header"></i>
<div class="header badge" v-show="beamerPending > 0">{{ beamerPending }}</div>
</a>
</template>

<script>
import profile from 'utils/profile'
import { getBeamerUnreadCount } from 'api/beamer'

export default {
props: [ 'minimal' ], // minimal = header in /developer page
data(){
return{
beamerPending: 0,
}
},
async mounted(){
const profileRes = await profile.getProfile()
const beamerRes = await getBeamerUnreadCount({user_id: profileRes.user.user_id})
this.beamerPending = beamerRes.body.count
},
methods:{
openBeamer () {
this.$emit('openBeamer')
this.beamerPending = 0
},
}
}
</script>
16 changes: 16 additions & 0 deletions src/components/Beamer/BeamerImageView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div class="beamer-image-view" @click="closeClicked">
<img :src="imgUrl" class="rounded" />
</div>
</template>

<script>
export default {
props: ['imgUrl'],
methods:{
closeClicked(){
this.$emit('closeClicked')
}
}
}
</script>
77 changes: 77 additions & 0 deletions src/components/Beamer/BeamerMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div class="beamer-message">
<h2 v-if="message.translation.linkUrl" class="text-18px text-grey-80 font-medium mb-10px">
<a
@click="linkClicked"
:href="message.translation.linkUrl"
:target="!isOfficientLink ? '_blank' : ''"
class="link"
>
{{message.translation.title}}
</a>
</h2>
<h2 v-else class="text-18px text-grey-80 font-medium mb-10px">{{message.translation.title}}</h2>

<span class="category" :class="message.category.toLowerCase()">{{ message.category }}</span>
<span class="feature-date">{{ message.date | dateFormat('LL') }}</span>
<div class="message-text">
<!-- makes sure text is correctly split up in paragraphs -->
<p v-for="(paragraph, i) of message.translation.content.split('\n')" :key="i">
<!-- makes sure *bold text* is bold -->
<span v-for="(part, i) of paragraph.split('*')" :key="`part_${i}`" :class="{'font-bold': i%2!=0}">
{{part}}
</span>
</p>
</div>
<div v-if="message.translation.images" class="text-center">
<figure
class="beamer-figure max-w-full cursor-pointer rounded-4px"
v-for="img of message.translation.images"
:key="img"
@click="imgClicked(img)"
>
<img
:src="img"
class="beamer-image max-w-full block rounded-4px"
/>
</figure>
</div>
<div v-if="message.translation.linkUrl" class="message-footer">
<a
@click="linkClicked"
:href="message.translation.linkUrl"
:target="!isOfficientLink ? '_blank' : ''"
class="link"
>
<span>{{ message.translation.linkText || message.translation.linkUrl }}</span><!--
--><span class="valign-middle ml-5px"><i class="mdi mdi-angle-right bold valign-middle"></i></span>
</a>
</div>
</div>
</template>

<script>
export default {
props: ['message'],
data(){
return{
isOfficientLink: false
}
},
mounted(){
if(this.message.translation.linkUrl){
this.isOfficientLink = this.message.translation.linkUrl.charAt(0) == '/'
}
},
methods:{
linkClicked(){
if(this.isOfficientLink){
this.$emit('requestClose')
}
},
imgClicked(url){
this.$emit('imgClicked', url)
}
}
}
</script>
137 changes: 137 additions & 0 deletions src/components/Beamer/BeamerSidebar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<template>
<div class="beamer">
<BeamerImageView
v-if="showImageView"
:imgUrl="imageViewUrl"
@closeClicked="showImageView = false"
/>
<div class="beamer-overlay" @click="closeClicked"></div>
<div class="beamer-aside">
<div class="beamer-header">
<span>{{ $t('WHATS_NEW_IN_OFFICIENT') }}</span>
<i class="mdi mdi-close" @click="closeClicked"></i>
</div>
<div class="beamer-body" ref="beamerBody">
<loading v-if="!initialLoad" class="absolute-center"/>
<BeamerMessage
v-for="message of posts"
:key="message.id"
:message="message"
@requestClose="closeClicked"
@imgClicked="imgClicked"
/>
<loading v-if="loadingNext" class="w-full h-30px mb-15px"/>
</div>
</div>
</div>
</template>

<script>
import { getBeamerPosts } from 'api/beamer'
import profile from 'utils/profile'
import BeamerMessage from './BeamerMessage'
import BeamerImageView from './BeamerImageView'
import { freezeBackground, unfreezeBackground } from 'utils/modals'

export default {
components: { BeamerMessage, BeamerImageView },
data(){
return{
posts: [],
lastScrollPosition: 0,
page: -1,
language: 'en',
userId: -1,
initialLoad: false,
loadingNext: false,
allLoaded: false,
imageViewUrl: '',
showImageView: false
}
},
mounted(){
this.$refs.beamerBody.addEventListener('scroll', this.scrollHandler)
},
async created(){
document.addEventListener('keydown', this.escapeHandler)
freezeBackground()

const profileRes = await profile.getProfile()
this.language = profileRes.user.language || 'en'
this.userId = profileRes.user.user_id

await this.addPosts()
this.initialLoad = true
},
beforeDestroy () {
document.removeEventListener('keydown', this.escapeHandler)
this.$refs.beamerBody.removeEventListener('scroll', this.scrollHandler)
unfreezeBackground()
},
methods:{
translationsToObject(posts, language){
for(let post of posts){

let translation
let fallbackTranslation

for(let tl of post.translations){
if(tl.language.toLowerCase() == language.toLowerCase()){
translation = tl
}
if(tl.language.toLowerCase() == 'en'){
fallbackTranslation = tl
}
}

post.translation = fallbackTranslation
if(translation){
post.translation = translation
}
// other translations are not needed
delete post.translations
}
return posts
},
async addPosts(){
this.page++
const beamerPostsRes = await getBeamerPosts({user_id: this.userId, page: this.page})
this.posts = this.posts.concat(this.translationsToObject(beamerPostsRes.body, this.language))
if(beamerPostsRes.body.length == 0){
// everything is loaded
this.allLoaded = true
}
},
async scrollHandler(){
// used for scroll pagination
const clientHeight = this.$refs.beamerBody.clientHeight
const scrollTop = this.$refs.beamerBody.scrollTop
const scrollHeight = this.$refs.beamerBody.scrollHeight

if(scrollHeight - clientHeight < scrollTop + 100){
// 100px to scroll left
// load the next posts
if(this.loadingNext || this.allLoaded){
return
}
this.loadingNext = true
await this.addPosts()
this.loadingNext = false
}

},
escapeHandler(e){
if (e.key === 'Escape') {
this.closeClicked()
}
},
imgClicked(url){
this.imageViewUrl = url
this.showImageView = true
},
closeClicked(){
this.$emit('closeClicked')
}
}
}
</script>