Client: Guilds Discovery (#8529)

* wip: add guilds discovery page

* add public guilds page

* fix and add tests for the groups utilities mixin
This commit is contained in:
Matteo Pagliazzi
2017-03-03 19:38:17 +01:00
committed by GitHub
parent 3629f7f8a5
commit dc8598ae81
15 changed files with 209 additions and 30 deletions

View File

@@ -0,0 +1,61 @@
import groupsUtilities from 'client/mixins/groupsUtilities';
import { TAVERN_ID } from 'common/script/constants';
import Vue from 'vue';
describe('Groups Utilities Mixin', () => {
let instance, user;
before(() => {
instance = new Vue({
mixins: [groupsUtilities],
});
user = {
_id: '123',
party: {
_id: '456',
},
guilds: ['789'],
};
});
describe('isMemberOfGroup', () => {
it('registers as a method', () => {
expect(instance.isMemberOfGroup).to.be.a.function;
});
it('returns true when the group is the Tavern', () => {
expect(instance.isMemberOfGroup(user, {
_id: TAVERN_ID,
})).to.equal(true);
});
it('returns true when the group is the user\'s party', () => {
expect(instance.isMemberOfGroup(user, {
type: 'party',
_id: user.party._id,
})).to.equal(true);
});
it('returns false when the group is not the user\'s party', () => {
expect(instance.isMemberOfGroup(user, {
type: 'party',
_id: 'not my party',
})).to.equal(false);
});
it('returns true when the group is not a guild of which the user is a member', () => {
expect(instance.isMemberOfGroup(user, {
type: 'guild',
_id: user.guilds[0],
})).to.equal(true);
});
it('returns false when the group is not a guild of which the user is a member', () => {
expect(instance.isMemberOfGroup(user, {
type: 'guild',
_id: 'not my guild',
})).to.equal(false);
});
});
});

View File

@@ -0,0 +1,7 @@
.label-primary {
font-weight: bold;
}
.nested-field {
padding-left: 1.5rem;
}

View File

@@ -1,5 +1,6 @@
// CSS that doesn't belong to any specific Vue compoennt
@import './utilities';
@import './forms';
@import './loading-screen';
body {

View File

@@ -30,7 +30,7 @@
// & { @import "~semantic-ui-less/definitions/elements/loader"; }
// & { @import "~semantic-ui-less/definitions/elements/rail"; }
// & { @import "~semantic-ui-less/definitions/elements/reveal"; }
// & { @import "~semantic-ui-less/definitions/elements/segment"; }
& { @import "~semantic-ui-less/definitions/elements/segment"; }
// & { @import "~semantic-ui-less/definitions/elements/step"; }
/* Collections */

View File

@@ -9,35 +9,35 @@
.ui.form
.field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label.label-primary(v-once) {{ $t('pets') }}
.field.nested-field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label(v-once) {{ $t('hatchingPotions') }}
.field.nested-field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label(v-once) {{ $t('quest') }}
.field.nested-field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label(v-once) {{ $t('special') }}
.field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label.label-primary(v-once) {{ $t('mounts') }}
.field.nested-field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label(v-once) {{ $t('hatchingPotions') }}
.field.nested-field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label(v-once) {{ $t('quest') }}
.field.nested-field
.ui.checkbox
input(type='checkbox')
input(type="checkbox")
label(v-once) {{ $t('special') }}
.thirteen.wide.column
@@ -60,18 +60,9 @@
h2 Mounts
h2 Quest Mounts
h2 Rare Mounts
</template>
<style>
.label-primary {
font-weight: bold;
}
.nested-field {
padding-left: 1.5rem;
}
.inventory-item-container {
padding: 20px;
border: 1px solid;

View File

@@ -0,0 +1,47 @@
<template lang="pug">
.ui.grid
.three.wide.column
.ui.left.icon.input
i.search.icon
input(type="text", :placeholder="$t('search')")
h3(v-once) {{ $t('filter') }}
.ui.form
h4 Interests
.field
.ui.checkbox
input(type="checkbox")
label(v-once) Habitica Official
.field
.ui.checkbox
input(type="checkbox")
label(v-once) Nature
.field
.ui.checkbox
input(type="checkbox")
label(v-once) Animals
.thirteen.wide.column
h2(v-once) {{ $t('publicGuilds') }}
public-guild-item(v-for="guild in guilds", :key='guild._id', :guild="guild")
</template>
<script>
import { mapState, mapActions } from '../../../../store';
import PublicGuildItem from './publicGuildItem';
export default {
components: { PublicGuildItem },
computed: {
...mapState(['guilds']),
},
methods: {
...mapActions({
fetchGuilds: 'guilds:fetchAll',
}),
},
created () {
if (!this.guilds) this.fetchGuilds();
},
};
</script>

View File

@@ -0,0 +1,24 @@
<template lang="pug">
.ui.clearing.raised.segment
.ui.right.floated.button(:class="[isMember ? 'red' : 'green']") {{ isMember ? $t('leave') : $t('join') }}
.floated
// TODO v-once?
h3.ui.header {{ guild.name }}
p {{ guild.description }}
</template>
<script>
import { mapState } from '../../../../store';
import groupUtilities from '../../../../mixins/groupsUtilities';
export default {
mixins: [groupUtilities],
props: ['guild'],
computed: {
...mapState(['user']),
isMember () {
return this.isMemberOfGroup(this.user, this.guild);
},
},
};
</script>

View File

@@ -1,9 +1,11 @@
<template lang="pug">
.row
.sixteen.wide.column
.ui.secondary.menu
.ui.secondary.menu.center-content
router-link.item(:to="{name: 'tavern'}")
span(v-once) {{ $t('tavern') }}
router-link.item(:to="{name: 'guilds'}")
span(v-once) {{ $t('guilds') }}
router-link.item(:to="{name: 'inbox'}")
span(v-once) {{ $t('inbox') }}

View File

@@ -0,0 +1,25 @@
// TODO if we only have a single method here, move it to an utility
// a full mixin is not needed
import { TAVERN_ID } from '../../common/script/constants';
export default {
methods: {
isMemberOfGroup (user, group) {
if (group._id === TAVERN_ID) return true;
// If the group is a guild, just check for an intersection with the
// current user's guilds, rather than checking the members of the group.
if (group.type === 'guild') {
return user.guilds.indexOf(group._id) !== -1;
}
// Similarly, if we're dealing with the user's current party, return true.
if (group.type === 'party') {
return user.party._id === group._id;
}
return false;
},
},
};

View File

@@ -18,6 +18,7 @@ import SocialContainer from './components/social/index';
import TavernPage from './components/social/tavern';
import InboxPage from './components/social/inbox/index';
import InboxConversationPage from './components/social/inbox/conversationPage';
import GuildsDiscoveryPage from './components/social/guilds/discovery/index';
Vue.use(VueRouter);
@@ -60,7 +61,17 @@ export default new VueRouter({
},
{ name: 'challenges', path: 'challenges', component: Page },
{ name: 'party', path: 'party', component: Page },
{ name: 'guilds', path: 'guilds', component: Page },
{
path: 'guilds',
component: EmptyView,
children: [
{
name: 'guilds',
path: '',
component: GuildsDiscoveryPage,
},
],
},
],
},
{

View File

@@ -0,0 +1,6 @@
import axios from 'axios';
export async function fetchAll (store) {
let response = await axios.get('/api/v3/groups?type=publicGuilds');
store.state.guilds = response.data.data;
}

View File

@@ -1,13 +1,16 @@
import { flattenAndNamespace } from '../helpers/internals';
import * as tasks from './tasks';
import * as user from './user';
// Actions should be named as 'actionName' and can be accessed as 'namespace.actionName'
// Example: fetch in user.js -> 'user.fetch'
import * as user from './user';
import * as tasks from './tasks';
import * as guilds from './guilds';
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
// Example: fetch in user.js -> 'user:fetch'
const actions = flattenAndNamespace({
user,
tasks,
guilds,
});
export default actions;

View File

@@ -1,8 +1,8 @@
import { flattenAndNamespace } from '../helpers/internals';
import * as user from './user';
// Getters should be named as 'getterName' and can be accessed as 'namespace.getterName'
// Example: gems in user.js -> 'user.gems'
// Getters should be named as 'getterName' and can be accessed as 'namespace:getterName'
// Example: gems in user.js -> 'user:gems'
const getters = flattenAndNamespace({
user,

View File

@@ -14,10 +14,10 @@ Example:
Result:
getters
user.gems
user.tasks
tasks.todos
tasks.dailys
user:gems
user:tasks
tasks:todos
tasks:dailys
*/
export function flattenAndNamespace (namespaces) {
let result = {};

View File

@@ -5,6 +5,7 @@ const state = {
title: 'Habitica',
user: null,
tasks: null, // user tasks
guilds: null, // list of public guilds, not fetched initially
// content data, frozen to prevent Vue from modifying it since it's static and never changes
// TODO apply freezing to the entire codebase (the server) and not only to the client side?
// NOTE this takes about 10-15ms on a fast computer