Migration to typescript: first step

Add vue cli typescript support
Rename .js to .ts
Use class and annotations in App and NavBar
Add tslint
This commit is contained in:
Chocobozzz
2018-12-21 15:41:34 +01:00
parent da817d35c4
commit b409a5583d
25 changed files with 712 additions and 296 deletions

View File

@@ -14,15 +14,15 @@
>
<v-list-tile avatar v-if="actor" slot="activator">
<v-list-tile-avatar>
<img v-if="!actor.avatar"
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="actor.avatar"
>
</v-list-tile-avatar>
<img v-if="!actor.avatar"
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="actor.avatar"
>
</v-list-tile-avatar>
<v-list-tile-content @click="$router.push({name: 'Account', params: { name: actor.username }})">
<v-list-tile-title>{{ this.displayed_name }}</v-list-tile-title>
@@ -31,11 +31,11 @@
<v-list-tile avatar v-if="actor">
<v-list-tile-avatar>
<img
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
</v-list-tile-avatar>
<img
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Autre identité</v-list-tile-title>
@@ -44,8 +44,8 @@
<v-list-tile @click="$router.push({ name: 'Identities' })">
<v-list-tile-action>
<v-icon>group</v-icon>
</v-list-tile-action>
<v-icon>group</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Identities</v-list-tile-title>
</v-list-tile-content>
@@ -100,7 +100,7 @@
transition="scale-transition"
v-if="user"
>
<v-btn
<v-btn
slot="activator"
v-model="fab"
color="blue darken-2"
@@ -134,7 +134,8 @@
class="white--text"
v-translate="{
date: new Date().getFullYear(),
}">© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks
}">© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a
href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks
</span>
</v-footer>
<v-snackbar
@@ -148,75 +149,78 @@
</v-app>
</template>
<script>
import gql from 'graphql-tag';
import NavBar from '@/components/NavBar';
import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
<script lang="ts">
import NavBar from '@/components/NavBar.vue';
import { Component, Vue } from 'vue-property-decorator';
import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
export default {
name: 'app',
components: {
NavBar,
},
data() {
return {
drawer: false,
fab: false,
user: localStorage.getItem(AUTH_USER_ID),
items: [
{
icon: 'poll', text: 'Events', route: 'EventList', role: null,
},
{
icon: 'group', text: 'Groups', route: 'GroupList', role: null,
},
{
icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN',
},
{ icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
{ icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
{ icon: 'help', text: 'Help', role: null },
{ icon: 'phonelink', text: 'App downloads', role: null },
],
error: {
timeout: 3000,
show: false,
text: '',
@Component({
components: {
NavBar
}
})
export default class App extends Vue {
drawer = false
fab = false
user = localStorage.getItem(AUTH_USER_ID)
items = [
{
icon: 'poll', text: 'Events', route: 'EventList', role: null
},
show_new_event_button: false,
actor: localStorage.getItem(AUTH_USER_ACTOR),
};
},
methods: {
showMenuItem(elem) {
return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true;
},
getUser() {
return this.user === undefined ? false : this.user;
},
toggleDrawer() {
this.drawer = !this.drawer;
},
},
computed: {
displayed_name() {
return this.actor.display_name === null ? this.actor.username : this.actor.display_name;
},
},
};
{
icon: 'group', text: 'Groups', route: 'GroupList', role: null
},
{
icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN'
},
{ icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
{ icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
{ icon: 'help', text: 'Help', role: null },
{ icon: 'phonelink', text: 'App downloads', role: null }
]
error = {
timeout: 3000,
show: false,
text: ''
}
show_new_event_button = false
actor = localStorage.getItem(AUTH_USER_ACTOR)
get displayed_name () {
// FIXME: load actor
return 'no implemented'
// return this.actor.display_name === null ? this.actor.username : this.actor.display_name
}
showMenuItem (elem) {
// FIXME: load actor
return false
// return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true
}
getUser () {
return this.user === undefined ? false : this.user
}
toggleDrawer () {
this.drawer = !this.drawer
}
}
</script>
<style>
.router-enter-active, .router-leave-active {
transition-property: opacity;
transition-duration: .25s;
}
.router-enter-active, .router-leave-active {
transition-property: opacity;
transition-duration: .25s;
}
.router-enter-active {
transition-delay: .25s;
}
.router-enter-active {
transition-delay: .25s;
}
.router-enter, .router-leave-active {
opacity: 0
}
.router-enter, .router-leave-active {
opacity: 0
}
</style>

View File

@@ -74,69 +74,63 @@
</v-list>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn flat @click="notificationMenu = false"><translate>Close</translate></v-btn>
<v-btn color="primary" flat @click="notificationMenu = false"><translate>Save</translate></v-btn>
<v-btn flat @click="notificationMenu = false">
<translate>Close</translate>
</v-btn>
<v-btn color="primary" flat @click="notificationMenu = false">
<translate>Save</translate>
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
<v-btn v-if="!user" :to="{ name: 'Login' }"><translate>Login</translate></v-btn>
<v-btn v-if="!user" :to="{ name: 'Login' }">
<translate>Login</translate>
</v-btn>
</v-toolbar>
</template>
<script>
import {AUTH_USER_ACTOR, AUTH_USER_ID} from '@/constants';
import {SEARCH} from '@/graphql/search';
<style>
nav.v-toolbar .v-input__slot {
margin-bottom: 0;
}
</style>
export default {
name: 'NavBar',
props: {
toggleDrawer: {
type: Function,
required: true,
},
},
data() {
return {
notificationMenu: false,
notifications: [
{ header: 'Coucou' },
{ title: "T'as une notification", subtitle: 'Et elle est cool' },
],
model: null,
search: [],
searchText: null,
searchSelect: null,
actor: localStorage.getItem(AUTH_USER_ACTOR),
user: localStorage.getItem(AUTH_USER_ID),
};
},
apollo: {
search: {
query: SEARCH,
variables() {
return {
searchText: this.searchText,
};
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
import { SEARCH } from '@/graphql/search';
@Component({
apollo: {
search: {
query: SEARCH,
variables() {
return {
searchText: this.searchText,
};
},
skip() {
return !this.searchText;
},
},
skip() {
return !this.searchText;
},
},
},
watch: {
model(val) {
switch(val.__typename) {
case 'Event':
this.$router.push({ name: 'Event', params: { uuid: val.uuid } });
break;
case 'Actor':
this.$router.push({ name: 'Account', params: { name: this.username_with_domain(val) } });
break;
}
},
},
computed: {
items() {
}
})
export default class NavBar extends Vue {
@Prop({ required: true, type: Function }) toggleDrawer!: Function;
notificationMenu = false;
notifications = [
{ header: 'Coucou' },
{ title: 'T\'as une notification', subtitle: 'Et elle est cool' },
];
model = null;
search: any[] = [];
searchText: string | null = null;
searchSelect = null;
actor: string | null = localStorage.getItem(AUTH_USER_ACTOR);
user: string | null = localStorage.getItem(AUTH_USER_ID);
get items() {
return this.search.map(searchEntry => {
switch (searchEntry.__typename) {
case 'Actor':
@@ -148,22 +142,29 @@ export default {
}
return searchEntry;
});
},
},
methods: {
}
@Watch('model')
onModelChanged(val) {
switch (val.__typename) {
case 'Event':
this.$router.push({ name: 'Event', params: { uuid: val.uuid } });
break;
case 'Actor':
this.$router.push({ name: 'Account', params: { name: this.username_with_domain(val) } });
break;
}
}
username_with_domain(actor) {
return actor.preferredUsername + (actor.domain === null ? '' : `@${actor.domain}`);
},
}
enter() {
console.log('enter');
this.$apollo.queries.search.refetch();
this.$apollo.queries[ 'search' ].refetch();
}
},
};
</script>
<style>
nav.v-toolbar .v-input__slot {
margin-bottom: 0;
}
</style>
}
</script>

View File

@@ -11,14 +11,16 @@ import 'vuetify/dist/vuetify.min.css';
import App from '@/App.vue';
import router from '@/router';
// import store from './store';
import translations from '@/i18n/translations.json';
import { createProvider } from './vue-apollo';
const translations = require('@/i18n/translations.json');
Vue.config.productionTip = false;
Vue.use(VueMarkdown);
Vue.use(Vuetify);
const language = window.navigator.userLanguage || window.navigator.language;
const language = (window.navigator as any).userLanguage || window.navigator.language;
moment.locale(language);
Vue.filter('formatDate', value => (value ? moment(String(value)).format('LLLL') : null));
@@ -33,8 +35,8 @@ Vue.config.language = language.replace('-', '_');
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
el: '#app',
template: '<App/>',
apolloProvider: createProvider(),
components: { App },

View File

@@ -1,23 +1,23 @@
import Vue from 'vue';
import Router from 'vue-router';
import PageNotFound from '@/components/PageNotFound';
import Home from '@/components/Home';
import Event from '@/components/Event/Event';
import EventList from '@/components/Event/EventList';
import Location from '@/components/Location';
import CreateEvent from '@/components/Event/Create';
import CategoryList from '@/components/Category/List';
import CreateCategory from '@/components/Category/Create';
import Register from '@/components/Account/Register';
import Login from '@/components/Account/Login';
import Validate from '@/components/Account/Validate';
import ResendConfirmation from '@/components/Account/ResendConfirmation';
import SendPasswordReset from '@/components/Account/SendPasswordReset';
import PasswordReset from '@/components/Account/PasswordReset';
import Account from '@/components/Account/Account';
import CreateGroup from '@/components/Group/Create';
import Group from '@/components/Group/Group';
import GroupList from '@/components/Group/GroupList';
import PageNotFound from '@/components/PageNotFound.vue';
import Home from '@/components/Home.vue';
import Event from '@/components/Event/Event.vue';
import EventList from '@/components/Event/EventList.vue';
import Location from '@/components/Location.vue';
import CreateEvent from '@/components/Event/Create.vue';
import CategoryList from '@/components/Category/List.vue';
import CreateCategory from '@/components/Category/Create.vue';
import Register from '@/components/Account/Register.vue';
import Login from '@/components/Account/Login.vue';
import Validate from '@/components/Account/Validate.vue';
import ResendConfirmation from '@/components/Account/ResendConfirmation.vue';
import SendPasswordReset from '@/components/Account/SendPasswordReset.vue';
import PasswordReset from '@/components/Account/PasswordReset.vue';
import Account from '@/components/Account/Account.vue';
import CreateGroup from '@/components/Group/Create.vue';
import Group from '@/components/Group/Group.vue';
import GroupList from '@/components/Group/GroupList.vue';
import Identities from '../components/Account/Identities.vue';
Vue.use(Router);

13
js/src/shims-tsx.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}

4
js/src/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

View File

@@ -33,7 +33,6 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
const cache = new InMemoryCache({ fragmentMatcher });
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const token = localStorage.getItem(AUTH_TOKEN);
@@ -43,7 +42,9 @@ const authMiddleware = new ApolloLink((operation, forward) => {
},
});
return forward(operation);
if (forward) forward(operation);
return null;
});
const uploadLink = createLink({
@@ -60,6 +61,8 @@ const link = authMiddleware.concat(uploadLink);
// Config
const defaultOptions = {
cache,
link,
// You can use `https` for secure connection (recommended in production)
httpEndpoint,
// You can use `wss` for secure connection (recommended in production)
@@ -74,9 +77,8 @@ const defaultOptions = {
websocketsOnly: false,
// Is being rendered on the server?
ssr: false,
cache,
link,
defaultHttpLink: false,
connectToDevTools: true,
};
// Call this in the Vue app file
@@ -89,23 +91,18 @@ export function createProvider(options = {}) {
apolloClient.wsClient = wsClient;
// Create vue apollo provider
const apolloProvider = new VueApollo({
return new VueApollo({
defaultClient: apolloClient,
link,
cache,
connectToDevTools: true,
defaultOptions: {
$query: {
// fetchPolicy: 'cache-and-network',
},
},
// defaultOptions: {
// $query: {
// fetchPolicy: 'cache-and-network',
// },
// },
errorHandler(error) {
// eslint-disable-next-line no-console
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message);
},
});
return apolloProvider;
}
// Manually call this when user log in