Front-end fixes and updates

Especially Join/Leave event, Vue-Markdown replacement

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-02-25 17:20:06 +01:00
parent 3507438f17
commit c4e327508b
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
11 changed files with 772 additions and 950 deletions

1581
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,8 +13,8 @@
},
"dependencies": {
"apollo-absinthe-upload-link": "^1.5.0",
"apollo-cache-inmemory": "^1.4.2",
"apollo-client": "^2.4.12",
"apollo-cache-inmemory": "^1.4.3",
"apollo-client": "^2.4.13",
"apollo-link": "^1.2.8",
"apollo-link-http": "^1.5.11",
"apollo-link-state": "^0.4.2",
@ -25,37 +25,37 @@
"lodash": "^4.17.11",
"material-design-icons": "^3.0.1",
"ngeohash": "^0.6.3",
"register-service-worker": "^1.6.1",
"vue": "^2.6.3",
"register-service-worker": "^1.6.2",
"vue": "^2.6.7",
"vue-apollo": "^3.0.0-beta.28",
"vue-class-component": "^6.3.2",
"vue-class-component": "^7.0.1",
"vue-gettext": "^2.1.2",
"vue-markdown": "^2.2.4",
"vue-property-decorator": "^7.3.0",
"vue-router": "^3.0.2",
"vue-simple-markdown": "^1.0.8",
"vuex": "^3.1.0"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/lodash": "^4.14.120",
"@types/mocha": "^5.2.5",
"@vue/cli-plugin-babel": "^3.4.0",
"@vue/cli-plugin-e2e-nightwatch": "^3.4.0",
"@vue/cli-plugin-pwa": "^3.4.0",
"@vue/cli-plugin-typescript": "^3.4.0",
"@vue/cli-plugin-unit-mocha": "^3.4.0",
"@vue/cli-service": "^3.4.0",
"@types/lodash": "^4.14.121",
"@types/mocha": "^5.2.6",
"@vue/cli-plugin-babel": "^3.4.1",
"@vue/cli-plugin-e2e-nightwatch": "^3.4.1",
"@vue/cli-plugin-pwa": "^3.4.1",
"@vue/cli-plugin-typescript": "^3.4.1",
"@vue/cli-plugin-unit-mocha": "^3.4.1",
"@vue/cli-service": "^3.4.1",
"@vue/eslint-config-typescript": "^4.0.0",
"@vue/test-utils": "^1.0.0-beta.29",
"chai": "^4.2.0",
"dotenv-webpack": "^1.7.0",
"node-sass": "^4.11.0",
"patch-package": "^6.0.2",
"patch-package": "^6.0.4",
"sass-loader": "^7.1.0",
"tslint-config-airbnb": "^5.11.1",
"typescript": "^3.3.3",
"vue-template-compiler": "^2.6.3",
"webpack-bundle-analyzer": "^3.0.3"
"typescript": "^3.3.3333",
"vue-template-compiler": "^2.6.7",
"webpack-bundle-analyzer": "^3.0.4"
},
"browserslist": [
"> 1%",

View File

@ -5,7 +5,8 @@ const participantQuery = `
actor {
preferredUsername,
avatarUrl,
name
name,
id
}
`;
@ -117,23 +118,20 @@ export const EDIT_EVENT = gql`
`;
export const JOIN_EVENT = gql`
mutation JoinEvent($id: Int!, $actorId: Int!) {
mutation JoinEvent($eventId: Int!, $actorId: Int!) {
joinEvent(
id: $id,
eventId: $eventId,
actorId: $actorId
) {
actor {
${participantQuery}
},
role
}
}
`;
export const LEAVE_EVENT = gql`
mutation LeaveEvent($id: Int!, $actorId: Int!) {
mutation LeaveEvent($eventId: Int!, $actorId: Int!) {
leaveEvent(
id: $id,
eventId: $eventId,
actorId: $actorId
) {
actor {

View File

@ -2,7 +2,7 @@
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
// import * as VueGoogleMaps from 'vue2-google-maps';
import VueMarkdown from 'vue-markdown';
import VueSimpleMarkdown from 'vue-simple-markdown';
import Buefy from 'buefy'
import 'buefy/dist/buefy.css';
import GetTextPlugin from 'vue-gettext';
@ -14,7 +14,7 @@ const translations = require('@/i18n/translations.json');
Vue.config.productionTip = false;
Vue.use(VueMarkdown);
Vue.use(VueSimpleMarkdown);
Vue.use(Buefy, {
defaultContainerElement: '#mobilizon'
});

View File

@ -28,7 +28,8 @@ export const userRoutes = [
path: '/register/profile',
name: UserRouteName.REGISTER_PROFILE,
component: RegisterProfile,
props: true,
// We can only pass string values through params, therefore
props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true' }),
meta: { requiredAuth: false },
},
{
@ -56,8 +57,7 @@ export const userRoutes = [
path: '/validate/:token',
name: UserRouteName.VALIDATE,
component: Validate,
// We can only pass string values through params, therefore
props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true' }),
props: true,
meta: { requiresAuth: false },
},
{

View File

@ -12,16 +12,6 @@
<div class="columns is-mobile">
<div class="column">
<form v-if="!validationSent">
<div class="columns is-mobile is-centered">
<div class="column is-narrow">
<figure class="image is-64x64">
<transition name="avatar">
<v-gravatar v-bind="{email: email}" default-img="mp"></v-gravatar>
</transition>
</figure>
</div>
</div>
<b-field
:label="$gettext('Username')"
:type="errors.preferred_username ? 'is-danger' : null"

View File

@ -34,18 +34,13 @@ import {EventJoinOptions} from "../../types/event.model";
<script lang="ts">
// import Location from '@/components/Location';
import VueMarkdown from "vue-markdown";
import {CREATE_EVENT, EDIT_EVENT} from "@/graphql/event";
import {Component, Prop, Vue} from "vue-property-decorator";
import {Category, EventJoinOptions, EventStatus, EventVisibility, IEvent} from "@/types/event.model";
import {LOGGED_PERSON} from "@/graphql/actor";
import {IPerson} from "@/types/actor.model";
@Component({
components: {
VueMarkdown
}
})
@Component({})
export default class CreateEvent extends Vue {
@Prop({ required: false, type: String }) uuid!: string;

View File

@ -47,15 +47,22 @@
<span>{{ event.begins_on | formatDate }} - {{ event.ends_on | formatDate }}</span>
</div>
<p v-if="actorIsOrganizer()">
<translate>Vous êtes organisateur de cet événement.</translate>
<translate>You are an organizer.</translate>
</p>
<div v-else>
<p v-if="actorIsParticipant()">
<translate>Vous avez annoncé aller à cet événement.</translate>
<translate>You announced that you're going to this event.</translate>
</p>
<p v-else>
Vous y allez ?
<span>{{ event.participants.length }} personnes y vont.</span>
<translate>Are you going to this event?</translate><br />
<span>
<translate
:translate-n="event.participants.length"
translate-plural="%{event.participants.length} persons are going"
>
One person is going.
</translate>
</span>
</p>
</div>
<div v-if="!actorIsOrganizer()">
@ -66,7 +73,7 @@
</div>
<h2 class="subtitle">Details</h2>
<p v-if="event.description">
<vue-markdown :source="event.description"></vue-markdown>
<vue-simple-markdown :source="event.description"></vue-simple-markdown>
</p>
<h2 class="subtitle">Participants</h2>
<span v-if="event.participants.length === 0">No participants yet.</span>
@ -99,15 +106,10 @@ import { LOGGED_PERSON } from '@/graphql/actor';
import { IEvent, IParticipant } from '@/types/event.model';
import { JOIN_EVENT } from '@/graphql/event';
import { IPerson } from '@/types/actor.model';
import { RouteName } from '@/router'
// No typings for this component, so we use require
const VueMarkdown = require('vue-markdown');
import { RouteName } from '@/router';
import 'vue-simple-markdown/dist/vue-simple-markdown.css';
@Component({
components: {
VueMarkdown
},
apollo: {
event: {
query: FETCH_EVENT,
@ -152,13 +154,13 @@ export default class Event extends Vue {
await this.$apollo.mutate<IParticipant>({
mutation: JOIN_EVENT,
variables: {
id: this.event.id,
eventId: this.event.id,
actorId: this.loggedPerson.id,
},
update: (store, { data: { joinEvent } }) => {
const event = store.readQuery<IEvent>({ query: FETCH_EVENT });
if (event === null) {
console.error('Cannot update event participant cache, because of null value.')
console.error('Cannot update event participant cache, because of null value.');
return
}
@ -177,7 +179,7 @@ export default class Event extends Vue {
await this.$apollo.mutate<IParticipant>({
mutation: LEAVE_EVENT,
variables: {
id: this.event.id,
eventId: this.event.id,
actorId: this.loggedPerson.id,
},
update: (store, { data: { leaveEvent } }) => {

View File

@ -19,17 +19,14 @@
</template>
<script lang="ts">
import ngeohash from 'ngeohash';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import EventCard from '@/components/Event/EventCard.vue';
import { RouteName } from '@/router';
// VueMarkdown is untyped
const VueMarkdown = require('vue-markdown');
const ngeohash = require('ngeohash');
@Component({
components: {
VueMarkdown,
EventCard
}
})

View File

@ -28,14 +28,7 @@
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
// VueMarkdown is untyped
const VueMarkdown = require('vue-markdown')
@Component({
components: {
VueMarkdown
}
})
@Component({})
export default class CreateGroup extends Vue {
e1 = 0;
// FIXME: correctly type group

View File

@ -4,7 +4,7 @@ defmodule MobilizonWeb.Resolvers.Event do
"""
alias Mobilizon.Activity
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Actors.User
alias Mobilizon.Actors.{User, Actor}
# We limit the max number of events that can be retrieved
@event_max_limit 100
@ -100,8 +100,7 @@ defmodule MobilizonWeb.Resolvers.Event do
{:ok, %Participant{} = participant} <-
Mobilizon.Events.get_participant(event_id, actor_id),
{:only_organizer, false} <-
{:only_organizer,
Mobilizon.Events.list_organizers_participants_for_event(event_id) |> length == 1},
{:only_organizer, check_that_participant_is_not_only_organizer(event_id, actor_id)},
{:ok, _} <-
Mobilizon.Events.delete_participant(participant) do
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
@ -121,6 +120,19 @@ defmodule MobilizonWeb.Resolvers.Event do
{:error, "You need to be logged-in to leave an event"}
end
# We check that the actor asking to leave the event is not it's only organizer
# We start by fetching the list of organizers and if there's only one of them
# and that it's the actor requesting leaving the event we return true
@spec check_that_participant_is_not_only_organizer(integer(), integer()) :: boolean()
defp check_that_participant_is_not_only_organizer(event_id, actor_id) do
with [%Participant{actor: %Actor{id: participant_actor_id}}] <-
Mobilizon.Events.list_organizers_participants_for_event(event_id) do
participant_actor_id == actor_id
else
_ -> false
end
end
@doc """
Create an event
"""