Skip to content

Commit

Permalink
feat: Feed options
Browse files Browse the repository at this point in the history
- Tabs: Recent, Unread, Following
- Ability to follow projects
  • Loading branch information
netchampfaris committed Feb 24, 2023
1 parent f55576e commit b76278e
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 138 deletions.
6 changes: 6 additions & 0 deletions frontend/src/components/DiscussionList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ import PinIcon from '~icons/lucide/pin'
export default {
name: 'DiscussionList',
props: ['filters', 'routeName'],
expose: ['discussions'],
components: {
TextEditor,
Tooltip,
Expand Down Expand Up @@ -194,5 +195,10 @@ export default {
].join('\n')
},
},
computed: {
discussions() {
return this.$resources.discussions
},
},
}
</script>
225 changes: 211 additions & 14 deletions frontend/src/pages/Feed.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,230 @@
<template>
<DiscussionList
class="mx-auto max-w-4xl sm:px-5"
routeName="ProjectDiscussion"
:filters="selectedTeam.value ? { team: selectedTeam.value } : null"
/>
<header class="sticky top-0 z-10 border-b bg-white py-3 px-4 sm:px-5">
<div class="flex items-center justify-between">
<div class="ml-3 flex">
<h1 class="mr-4 text-2xl font-semibold">Home</h1>

<div class="flex space-x-0.5">
<Button
appearance="minimal"
v-for="option in feedOptions"
:key="option.value"
:active="feedType === option.value"
@click="$router.replace({ params: { feedType: option.value } })"
>
{{ option.label }}
</Button>
</div>
</div>
<button
@click="showCommandPalette"
class="hidden w-full max-w-[20rem] rounded-md focus:outline-none focus:ring focus:ring-gray-300 md:block"
>
<Input
:placeholder="searchPlaceholder"
icon-left="search"
class="cursor-pointer"
:disabled="true"
/>
</button>
</div>
</header>
<div class="mt-1">
<div
class="mx-auto -mt-1 flex max-w-4xl items-center justify-between pt-2 pb-2 sm:px-5"
v-if="feedType === 'following'"
>
<div class="text-base text-gray-700">
<div v-if="$resources.followedProjects.data?.length === 0">
You are not following any projects yet. Follow projects to see their
discussions here.
</div>

<div class="text-base text-gray-700" v-else>
You are following
<span class="font-semibold">
{{ $resources.followedProjects.data?.length }}
</span>

{{
$resources.followedProjects.data?.length === 1
? 'project.'
: 'projects.'
}}
</div>
</div>

<Button icon-left="plus" @click="followProjectsDialog = true">
Follow Projects
</Button>
</div>
<DiscussionList
ref="discussionList"
class="mx-auto max-w-4xl sm:px-5"
routeName="ProjectDiscussion"
:filters="filters"
/>
<Dialog
v-model="followProjectsDialog"
:options="{ title: 'Select projects to follow' }"
@close="$refs.discussionList.discussions.reload()"
>
<template #body-content>
<div>
<div class="mt-1 gap-2">
<div
v-for="team in projectOptions"
:key="team.group"
@click="projects = projects.filter((p) => p !== project)"
class="mb-4"
>
<div class="text-lg font-semibold text-gray-900">
{{ team.group }}
</div>
<div class="mt-1 divide-y divide-gray-100">
<div
class="flex items-center justify-between py-0.5"
v-for="project in team.items"
:key="project.value"
>
<div class="text-base text-gray-800">
{{ project.label }}
</div>
<Button
v-if="isFollowed(project.value)"
icon="check"
appearance="minimal"
label="Unfollow project"
@click="unfollowProject(project.value)"
:loading="
$resources.followedProjects.delete.loading &&
$resources.followedProjects.delete.params.name ==
project.followId
"
/>
<Button
v-else
icon="plus"
label="Follow project"
appearance="minimal"
@click="followProject(project.value)"
:loading="
$resources.followedProjects.insert.loading &&
$resources.followedProjects.insert.params?.doc?.name ==
project.value
"
/>
</div>
</div>
</div>
</div>
</div>
</template>
</Dialog>
</div>
</template>
<script>
import Breadcrumbs from '@/components/Breadcrumbs.vue'
import DiscussionList from '@/components/DiscussionList.vue'
import { activeTeams } from '@/data/teams'
import { scrollTo as scrollContainerTo } from '@/utils/scrollContainer'
import { getTeamProjects } from '@/data/projects'
import { Autocomplete } from 'frappe-ui'
import { getPlatform } from '@/utils'
import { showCommandPalette } from '@/components/CommandPalette.vue'
let projectFollowId = {}
export default {
name: 'Home',
props: ['postId'],
components: { Breadcrumbs, DiscussionList },
props: ['feedType'],
components: { Breadcrumbs, DiscussionList, Autocomplete },
data() {
return {
selectedTeam: { label: null, value: '' },
activeTeams,
followProjectsDialog: false,
projects: [],
selectedProject: null,
feedOptions: [
{
label: 'Recent',
value: 'recent',
},
{
label: 'Unread',
value: 'unread',
},
{
label: 'Following',
value: 'following',
},
],
}
},
watch: {
selectedProject(value) {
if (!value) return
if (!this.projects.includes(value)) {
this.projects.push(value)
}
this.selectedProject = null
},
},
resources: {
followedProjects() {
return {
type: 'list',
doctype: 'GP Followed Project',
fields: ['name', 'project', 'project.title'],
auto: true,
pageLength: 1000,
onSuccess(data) {
projectFollowId = {}
data.forEach((p) => {
projectFollowId[p.project] = p.name
})
},
}
},
},
methods: {
scrollToTop() {
scrollContainerTo({
top: 0,
behavior: 'smooth',
followProject(project) {
this.$resources.followedProjects.insert.submit({
project,
})
},
unfollowProject(project) {
let followId = projectFollowId[project]
if (!followId) return
this.$resources.followedProjects.delete.submit(followId)
},
isFollowed(project) {
let followedProjects = (this.$resources.followedProjects.data || []).map(
(p) => parseInt(p.project)
)
return followedProjects.includes(project)
},
showCommandPalette,
},
computed: {
filters() {
return this.feedType ? { feed_type: this.feedType } : null
},
projectOptions() {
return activeTeams.value.map((team) => ({
group: team.title,
items: getTeamProjects(team.name).map((project) => ({
label: project.title,
value: project.name,
followId: projectFollowId[project.name],
})),
}))
},
searchPlaceholder() {
let platform = getPlatform()
if (platform == 'mac') {
return 'Jump to project or team (⌘+K)'
}
return 'Jump to project or team (Ctrl+K)'
},
},
pageMeta() {
return {
Expand Down
118 changes: 2 additions & 116 deletions frontend/src/pages/Home.vue
Original file line number Diff line number Diff line change
@@ -1,128 +1,14 @@
<template>
<div>
<header class="sticky top-0 z-10 border-b bg-white py-3 px-4 sm:px-5">
<div class="flex items-center justify-between">
<div class="-ml-3">
<Button
@click="showCustomiseHomeDialog = true"
icon-right="chevron-down"
appearance="minimal"
>
<h1 class="text-2xl font-semibold">
{{ title }}
</h1>
</Button>
</div>
<button
@click="showCommandPalette"
class="hidden w-full max-w-[20rem] rounded-md focus:outline-none focus:ring focus:ring-gray-300 md:block"
>
<Input
:placeholder="searchPlaceholder"
icon-left="search"
class="cursor-pointer"
:disabled="true"
/>
</button>
</div>
</header>

<HomeProjects v-if="homePage === 'projects'" />
<Feed v-if="homePage === 'feed'" />

<Dialog
:options="{
title: 'Home',
}"
v-model="showCustomiseHomeDialog"
>
<template #body-content>
<p class="text-base text-gray-600">
Customise what you see on your homepage
</p>
<div class="mt-4 space-y-2">
<button
v-for="option in homePageOptions"
class="flex w-full items-center rounded-md border px-4 py-3"
:key="option.value"
@click="homePage = option.value"
:class="
homePage == option.value
? 'border-blue-500 ring-2 ring-blue-100'
: 'hover:border-gray-400'
"
>
<CheckCircle
v-if="homePage == option.value"
class="mr-2 h-4 w-4 text-blue-500"
/>
<Circle v-else class="mr-2 h-4 w-4 text-gray-500" />
<span class="text-base text-gray-900">
{{ option.label }}
</span>
</button>
</div>
</template>
</Dialog>
</div>
<Feed :feedType="feedType" />
</template>
<script>
import Feed from './Feed.vue'
import HomeProjects from './HomeProjects.vue'
import { showCommandPalette } from '@/components/CommandPalette.vue'
import { getPlatform } from '@/utils'
import { useLocalStorage } from '@/utils/composables'
import CircleDot from '~icons/lucide/circle-dot'
import Circle from '~icons/lucide/circle'
import CheckCircle from '~icons/lucide/check-circle2'
export default {
name: 'Home',
props: ['feedType'],
components: {
HomeProjects,
Feed,
CircleDot,
Circle,
CheckCircle,
},
data() {
// options: projects, feed
let homePage = useLocalStorage('homePage', 'projects')
return {
homePage,
showCustomiseHomeDialog: false,
homePageOptions: [
{
label: 'Pinned, Active & Recent Projects',
value: 'projects',
title: 'Projects',
},
{
label: 'Discussion feed sorted by most recent',
value: 'feed',
title: 'Feed',
},
],
}
},
methods: {
showCommandPalette,
},
computed: {
title() {
return (
this.homePageOptions.find((option) => option.value === this.homePage)
?.title || 'Home'
)
},
searchPlaceholder() {
let platform = getPlatform()
if (platform == 'mac') {
return 'Jump to project or team (⌘+K)'
}
return 'Jump to project or team (Ctrl+K)'
},
},
}
</script>
Loading

0 comments on commit b76278e

Please sign in to comment.