diff --git a/src/api.js b/src/api.js index de66b976355aff6b346246a3bf8819868ff2ce77..5f22e58803540389dcdd1322c4b551b1e146210d 100644 --- a/src/api.js +++ b/src/api.js @@ -116,6 +116,16 @@ export const api = { return null } }, + async getUserLikedModels () { + const token = await localStorage.getItem('token') + const url = serverurl + 'vote/likedModels' + try { + const response = await fetch(url, { headers: { Authorization: 'Bearer ' + token } }) + return await response.json() + } catch (error) { + return null + } + }, async getModel (id) { const url = serverurl + 'models' + '?id=' + id const response = await fetch(url) @@ -304,7 +314,7 @@ export const api = { } }, async postModel (model) { - const url = this.$serverurl + 'models' + const url = serverurl + 'models' const token = await localStorage.getItem('token') try { const response = await fetch(url, { @@ -321,7 +331,8 @@ export const api = { performance: model.performance, performanceUnit: model.performanceUnit, parameterCount: model.parameterCount, - tags: model.tags + tags: model.tags, + performanceLowerIsBetter: model.performanceLowerIsBetter }) }) return await response.json() @@ -346,7 +357,8 @@ export const api = { performance: model.performance, performanceUnit: model.performanceUnit, parameterCount: model.parameterCount, - tags: model.tags + tags: model.tags, + performanceLowerIsBetter: model.performanceLowerIsBetter }) }) try { @@ -390,5 +402,49 @@ export const api = { } catch (error) { return null } + }, + async getVoteStatus (id) { + const url = serverurl + 'vote' + '?id=' + id + const token = await localStorage.getItem('token') + const response = await fetch(url, { + headers: { + Authorization: 'Bearer ' + token + } + }) + try { + return await response.json() + } catch (error) { + return null + } + }, + async addVote (id, isDown) { + const url = serverurl + 'vote' + '?id=' + id + '&isDown=' + isDown + const token = await localStorage.getItem('token') + const response = await fetch(url, { + method: 'PUT', + headers: { + Authorization: 'Bearer ' + token + } + }) + try { + return await response.json() + } catch (error) { + return null + } + }, + async removeVote (id, isDown) { + const url = serverurl + 'vote' + '?id=' + id + '&isDown=' + isDown + const token = await localStorage.getItem('token') + const response = await fetch(url, { + method: 'DELETE', + headers: { + Authorization: 'Bearer ' + token + } + }) + try { + return await response.json() + } catch (error) { + return null + } } } diff --git a/src/components/Vote.vue b/src/components/Vote.vue new file mode 100644 index 0000000000000000000000000000000000000000..79a910d933596111a570070c5333490febaf8d10 --- /dev/null +++ b/src/components/Vote.vue @@ -0,0 +1,68 @@ +<template> + <div class="vote" :key="voteStatus.status"> + <h1 class="title is-4">{{localVoteCount}} votes</h1> + <div v-if="voteStatus.status === 'none'"> + <b-button size="is-small" icon-left="thumb-down-outline" style="top:-2px" @click="addDownVote"/> + <b-button size="is-small" icon-left="thumb-up-outline" style="top:-2px" @click="addUpVote"/> + </div> + <div v-else-if="voteStatus.status === 'up'"> + <b-button size="is-small" icon-left="thumb-down-outline" style="top:-2px" disabled/> + <b-button size="is-small" icon-left="thumb-up" style="top:-2px" @click="removeUpVote" type="is-success"/> + </div> + <div v-else-if="voteStatus.status === 'down'"> + <b-button size="is-small" icon-left="thumb-down" style="top:-2px" @click="removeDownVote" type="is-danger"/> + <b-button size="is-small" icon-left="thumb-up-outline" style="top:-2px" disabled/> + </div> + </div> +</template> + +<script> +import { api } from '@/api.js' + +export default { + name: 'vote', + props: ['modelId', 'voteCount'], + data () { + return { + voteStatus: { + status: 'none' + }, + localVoteCount: 0 + } + }, + async mounted () { + this.localVoteCount = this.voteCount + this.voteStatus = await api.getVoteStatus(this.modelId) + }, + methods: { + async addUpVote () { + var response = await api.addVote(this.modelId, false) + if (response.error) return + this.localVoteCount += 1 + this.voteStatus = await api.getVoteStatus(this.modelId) + }, + async addDownVote () { + var response = await api.addVote(this.modelId, true) + if (response.error) return + this.localVoteCount -= 1 + this.voteStatus = await api.getVoteStatus(this.modelId) + }, + async removeUpVote () { + var response = await api.removeVote(this.modelId, false) + if (response.error) return + this.localVoteCount -= 1 + this.voteStatus = await api.getVoteStatus(this.modelId) + }, + async removeDownVote () { + var response = await api.removeVote(this.modelId, true) + if (response.error) return + this.localVoteCount += 1 + this.voteStatus = await api.getVoteStatus(this.modelId) + } + } +} +</script> + +<style> + +</style> diff --git a/src/views/Account.vue b/src/views/Account.vue index 95cd4146f4b1828112193e3e274bc6520db6acc0..a5e6e57b9b7db5dc54a3ed1dcc662b153782b234 100644 --- a/src/views/Account.vue +++ b/src/views/Account.vue @@ -33,46 +33,97 @@ </div> <div class="box"> <h1 class="title is-4">My models</h1> - <ModelTable/> + <b-table :data="models" :loading="isLoading" striped hoverable> + <template slot-scope="props"> + <b-table-column field="name" label="Model name" searchable sortable> + {{ props.row.name }} <b-icon v-if="props.row.isVerified" icon="check"/> + </b-table-column> + + <b-table-column field="vote" label="Votes" sortable> + {{ props.row.votes }} + </b-table-column> + + <b-table-column field="date" label="Last modification" centered sortable> + {{ new Date(props.row.lastModified).toLocaleDateString() }} + </b-table-column> + + <b-table-column field="date" label="Added" centered sortable> + {{ new Date(props.row.added).toLocaleDateString() }} + </b-table-column> + + <b-table-column label="" centered> + <b-button class="actionButton" icon-left="eye" type="is-info" tag="router-link" :to="{ name: 'Model', query: { id: props.row.id } }" outlined/> + <b-button class="actionButton" icon-left="pencil" type="is-warning" tag="router-link" :to="{ name: 'ModelEdit', params: {model: props.row} }" outlined/> + <b-button class="actionButton" icon-left="delete" type="is-danger" @click="removePrompt(props.row.id, props.row.name)" outlined/> + </b-table-column> + </template> + </b-table> </div> <div class="box"> <h1 class="title is-4">Liked models</h1> + <b-table :data="likedModels" :loading="isLoading" striped hoverable> + <template slot-scope="props"> + <b-table-column field="name" label="Model name" searchable sortable> + {{ props.row.name }} <b-icon v-if="props.row.isVerified" icon="check"/> + </b-table-column> + + <b-table-column field="vote" label="Votes" sortable> + {{ props.row.votes }} + </b-table-column> + + <b-table-column field="date" label="Last modification" centered sortable> + {{ new Date(props.row.lastModified).toLocaleDateString() }} + </b-table-column> + + <b-table-column field="date" label="Added" centered sortable> + {{ new Date(props.row.added).toLocaleDateString() }} + </b-table-column> + + <b-table-column label="" centered> + <b-button class="actionButton" icon-left="eye" type="is-info" tag="router-link" :to="{ name: 'Model', query: { id: props.row.id } }" outlined/> + </b-table-column> + </template> + </b-table> </div> </div> </template> <script> -import ModelTable from '@/components/ModelTable.vue' import { api } from '@/api.js' export default { name: 'Account', - components: { - ModelTable - }, data () { return { + isLoading: true, account: {}, - models: [] + models: [], + likedModels: [] } }, async mounted () { this.account = await api.getUser() this.models = await api.getUserModels() + this.likedModels = await api.getUserLikedModels() await localStorage.setItem('user_role', this.account.role) await localStorage.setItem('user_id', this.account.id) + this.isLoading = false }, methods: { - async getAccount () { - const token = await localStorage.getItem('token') - const url = this.$serverurl + 'user' - try { - const response = await fetch(url, { headers: { Authorization: 'Bearer ' + token } }) - return await response.json() - } catch (error) { - return null - } + removePrompt (id, name) { + this.$buefy.dialog.confirm({ + message: 'Delete ' + name + ' ?', + cancelText: 'Abort', + confirmText: 'Delete', + type: 'is-danger', + hasIcon: true, + onConfirm: async () => { + await api.removeModel(id) + this.$buefy.toast.open(name + ' deleted') + this.models = await api.getUserModels() + } + }) } } } @@ -82,4 +133,8 @@ export default { .account{ margin-top: 20px; } + +.actionButton { + margin-left:8px; +} </style> diff --git a/src/views/Model.vue b/src/views/Model.vue index bdd18397791c6b91bac9ad9c3a45ed280a35185d..b21cd698fc83d685684e676f51b1182b15a568fc 100644 --- a/src/views/Model.vue +++ b/src/views/Model.vue @@ -4,7 +4,7 @@ <h1 class="title is-1">Error</h1> <h2 class="subtitle">Cant find model</h2> </div> - <div v-else> + <div v-else-if="model"> <div class="columns box"> <div class="column is-two-thirds"> <h1 class="title is-1">{{model.name}} <b-tag v-if="model.isVerified" type="is-success" size="is-large">Verified</b-tag></h1> @@ -19,7 +19,7 @@ <br> <br> </div> - <h1 class="title is-4">{{model.votes}} votes <b-button size="is-small" icon-left="heart-outline" style="top:-2px"/></h1> + <Vote v-bind:modelId="model.id" v-bind:voteCount="model.votes"/> <small>Author: </small><strong>{{model.author.username}}</strong> <br> <small>Added: </small><strong>{{new Date(model.added).toLocaleDateString()}}</strong> @@ -73,17 +73,19 @@ <script> import Comments from '@/components/Comments.vue' +import Vote from '@/components/Vote.vue' import marked from 'marked' import { api } from '@/api.js' export default { name: 'Model', components: { - Comments + Comments, + Vote }, data () { return { - model: '', + model: null, isError: false, isLoading: true, isAuthor: false, diff --git a/src/views/ModelAdd.vue b/src/views/ModelAdd.vue index ea3716f577354bec04993295da213e6e49d3b52f..922ee8a636df54bd90f91e6f3766497d765f2211 100644 --- a/src/views/ModelAdd.vue +++ b/src/views/ModelAdd.vue @@ -18,6 +18,7 @@ <b-field> <b-input type="number" placeholder="98" max="10000000" step="0.01" v-model="model.performance"/> <b-autocomplete open-on-focus placeholder="accuracy" :data="performanceTypes" v-model="model.performanceUnit" maxlength="50"/> + <b-checkbox v-model="model.performanceLowerIsBetter">Lower is better</b-checkbox> </b-field> </b-field> diff --git a/src/views/ModelEdit.vue b/src/views/ModelEdit.vue index 5b1f89af37a11d122cdfe464ed0484e9cb349343..8ca5831aefbb5341ca2b548a66d6805ea561e847 100644 --- a/src/views/ModelEdit.vue +++ b/src/views/ModelEdit.vue @@ -20,6 +20,7 @@ <b-field> <b-input type="number" placeholder="98" max="10000000" step="0.01" v-model="model.performance"/> <b-autocomplete open-on-focus placeholder="accuracy" :data="performanceTypes" v-model="model.performanceUnit" maxlength="50"/> + <b-checkbox v-model="model.performanceLowerIsBetter">Lower is better</b-checkbox> </b-field> </b-field>