diff --git a/.env.development b/.env.development new file mode 100644 index 0000000000000000000000000000000000000000..32b1361ade9580debee6f6d8fd35d5dcca036948 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +VUE_APP_SERVER_URL = http://localhost:8181/v1/ \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000000000000000000000000000000000000..503dcc1a0f0ecca28b78d3ab664e0aaebc14b7a4 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +VUE_APP_SERVER_URL = /api/v1/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b2ed14075c38c2a71b424eb365428a32bf21407a..a52f921fbab6a16712218abb1da314afad9c463c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9783,6 +9783,11 @@ "object-visit": "^1.0.0" } }, + "marked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", diff --git a/package.json b/package.json index d5aa5b65c68d255c671406a8f6b2bf1d37a4793a..29ffb0d3b315ed1ce10ab3b935be5315af36309b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "buefy": "^0.8.17", "core-js": "^3.6.4", + "marked": "^1.0.0", "node-sass": "^4.14.0", "sass-loader": "^8.0.2", "vue": "^2.6.11", diff --git a/src/components/Comments.vue b/src/components/Comments.vue index 3253ef447ed8938e6df794232142e0219b8af05d..85c823308f6d56ba462ef69c21772411bf43ffb0 100644 --- a/src/components/Comments.vue +++ b/src/components/Comments.vue @@ -1,11 +1,11 @@ <template> <div class="comments" v-if="comments"> - <h1 class="title is-5">{{comments.length}} commentaires</h1> + <h1 class="title is-5">{{comments.length}} comments</h1> <article class="media" v-for="comment in comments" v-bind:key="comment.id"> <div class="media-content"> <div class="content"> <p> - <strong>{{comment.author}}</strong> - <small>{{comment.date}}</small> + <strong>{{comment.author}}</strong> - <small>{{new Date(comment.date).toLocaleDateString()}}</small> <br> {{comment.content}} </p> diff --git a/src/components/Filters.vue b/src/components/Filters.vue index ce9d76dd381b978301ecf5c347960c480f5c05b0..021763c143dca9c9d6e397a52dea1eb5832730de 100644 --- a/src/components/Filters.vue +++ b/src/components/Filters.vue @@ -1,7 +1,7 @@ <template> <b-menu> <div> - <b-menu-list label="Performance"> + <b-menu-list label="minimum performance"> <b-slider v-model="perfValue" lazy @change="changeTag"></b-slider> </b-menu-list> <b-menu-list v-for="tag in tags" v-bind:key="tag.name" :label="tag.name"> diff --git a/src/components/LayersEditor.vue b/src/components/LayersEditor.vue new file mode 100644 index 0000000000000000000000000000000000000000..088476b85157447de65c8b04473c7cb79b369f1f --- /dev/null +++ b/src/components/LayersEditor.vue @@ -0,0 +1,15 @@ +<template> + <div class="layersEditor"> + + </div> +</template> + +<script> +export default { + name: 'LayersEditor' +} +</script> + +<style> + +</style> diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue index 6a48e4bd5e8b3fc2c6bc8c98e96221bb73d1c711..44afa98208fb4d1b7ecd9ff64c61f00c33fcb952 100644 --- a/src/components/LoginForm.vue +++ b/src/components/LoginForm.vue @@ -2,22 +2,22 @@ <form action=""> <div class="modal-card" style="width: auto"> <header class="modal-card-head"> - <p class="modal-card-title">Se connecter</p> + <p class="modal-card-title">Login</p> </header> <section class="modal-card-body"> <b-field label="Email"> <b-input type="email" v-model="email" placeholder="email" required/> </b-field> - <b-field label="Mot de passe"> - <b-input type="password" v-model="password" password-reveal placeholder="mot de passe" required/> + <b-field label="Password"> + <b-input type="password" v-model="password" password-reveal placeholder="password" required/> </b-field> - <b-button class="is-text is-small">mot de passe oublié ?</b-button > + <b-button class="is-text is-small">forgot password ?</b-button > </section> <footer class="modal-card-foot"> - <button class="button is-primary" @click="onLogin">Connexion</button> + <button class="button is-primary" @click="onLogin">Login</button> </footer> </div> </form> diff --git a/src/components/MarkdownEditor.vue b/src/components/MarkdownEditor.vue new file mode 100644 index 0000000000000000000000000000000000000000..e0431d2f7db9d13c490e0201b0e7748bd519f505 --- /dev/null +++ b/src/components/MarkdownEditor.vue @@ -0,0 +1,38 @@ +<template> + <div class="markdownEditor container"> + <b-tabs> + <b-tab-item label="Editor" class="edit"> + <b-input type="textarea" v-model="input"/> + </b-tab-item> + + <b-tab-item label="Preview"> + <div class="content" v-html="compiledMarkdown"></div> + </b-tab-item> + </b-tabs> + </div> +</template> + +<script> +import marked from 'marked' + +export default { + name: 'MarkdownEditor', + props: { + input: { + type: String, + default: '# Readme' + } + }, + computed: { + compiledMarkdown: function () { + return marked(this.input) + } + } +} +</script> + +<style scoped> +.edit textarea { + min-height: 800px; +} +</style> diff --git a/src/components/ModelCard.vue b/src/components/ModelCard.vue index 2981f29c345bc4b8a9a261cfbaeb01bde5c2b530..2c56fec228b7d183d5b75a7187665d2cc65f02af 100644 --- a/src/components/ModelCard.vue +++ b/src/components/ModelCard.vue @@ -8,7 +8,7 @@ <p> <strong>{{model.name}}</strong> <br> - <small>{{model.author}}</small> - <small>{{model.modificationDate}}</small> + <small>{{model.author}}</small> - <small>{{new Date(model.modificationDate).toLocaleDateString()}}</small> <br> {{model.shortDescription}} </p> diff --git a/src/components/ModelTable.vue b/src/components/ModelTable.vue index 1345de204dc553d456813998263bebca785e5643..a6164b3a12506b3075fdb6961cce29d416c9eb13 100644 --- a/src/components/ModelTable.vue +++ b/src/components/ModelTable.vue @@ -1,7 +1,7 @@ <template> <b-table :data="models" :loading="isLoading" striped hoverable> <template slot-scope="props"> - <b-table-column field="name" label="Nom du modèle" searchable sortable> + <b-table-column field="name" label="Model name" searchable sortable> {{ props.row.name }} </b-table-column> @@ -9,11 +9,11 @@ {{ props.row.vote }} </b-table-column> - <b-table-column field="date" label="Date de modification" centered sortable> + <b-table-column field="date" label="Last modification" centered sortable> {{ new Date(props.row.modificationDate).toLocaleDateString() }} </b-table-column> - <b-table-column field="date" label="Date d'ajout" centered sortable> + <b-table-column field="date" label="Added" centered sortable> {{ new Date(props.row.addedDate).toLocaleDateString() }} </b-table-column> @@ -55,9 +55,9 @@ export default { removePrompt (id) { const name = this.models[id].name this.$buefy.dialog.confirm({ - message: 'Supprimer ' + name + ' ?', - cancelText: 'Annuler', - confirmText: 'Supprimer', + message: 'Delete ' + name + ' ?', + cancelText: 'Abort', + confirmText: 'Delete', type: 'is-danger', hasIcon: true, onConfirm: () => this.remove(id) @@ -73,10 +73,10 @@ export default { Authorization: 'Bearer ' + this.token } }) - this.$buefy.toast.open(name + ' supprimé') + this.$buefy.toast.open(name + ' deleted') this.getData() } catch (error) { - this.$buefy.toast.open('Erreur supprimer') + this.$buefy.toast.open('Delete error') } } } diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 9192095cbefd94d854b3a94c0733075b85593743..36571e436fdaf1ae914e935ddb9b8f5d0efea237 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -12,7 +12,7 @@ <template slot="start"> <b-navbar-item tag="div"> <b-field> - <b-input placeholder="Rechercher..." + <b-input placeholder="Search..." type="search" icon="magnify" v-model="search" @@ -27,10 +27,10 @@ Home </b-navbar-item> <b-navbar-item tag="router-link" :to="{ name: 'Search' }"> - Rechercher + Search </b-navbar-item> <b-navbar-item tag="router-link" :to="{ name: 'Docs' }"> - Documentation + Docs </b-navbar-item> <b-navbar-item tag="router-link" :to="{ name: 'About' }"> <b-icon icon="help-circle-outline"/> @@ -38,18 +38,18 @@ <b-navbar-item tag="div"> <div v-if="isLogged" class="buttons"> <b-button tag="router-link" :to="{ name: 'Account' }" type="is-light"> - <strong>Mon compte</strong> + <strong>Account</strong> </b-button> <b-button @click="logout" type="is-warning"> - Déconnexion + Logout </b-button> </div> <div v-else class="buttons"> <b-button @click="openRegisterPrompt" type="is-primary"> - <strong>S'inscrire</strong> + <strong>Sign up</strong> </b-button> <b-button @click="openLoginPrompt" type="is-light"> - Se connecter + Login </b-button> </div> </b-navbar-item> diff --git a/src/components/RegisterForm.vue b/src/components/RegisterForm.vue index 1ce52a575987d49fe513511d35343b91af33e4e9..7151ed2780f8568f5230a81c61952edc62b7089b 100644 --- a/src/components/RegisterForm.vue +++ b/src/components/RegisterForm.vue @@ -2,28 +2,28 @@ <form action=""> <div class="modal-card" style="width: auto"> <header class="modal-card-head"> - <p class="modal-card-title">S'inscrire</p> + <p class="modal-card-title">Sign up</p> </header> <section class="modal-card-body"> <b-field label="Email"> <b-input type="email" v-model="email" placeholder="email" required/> </b-field> - <b-field label="Nom d'utilisateur"> - <b-input v-model="username" placeholder="nom d'utilisateur" required/> + <b-field label="Username"> + <b-input v-model="username" placeholder="username" required/> </b-field> - <b-field label="Mot de passe"> - <b-input type="password" v-model="password" password-reveal placeholder="mot de passe" required/> + <b-field label="Password"> + <b-input type="password" v-model="password" password-reveal placeholder="password" required/> </b-field> - <b-field label="Confirmer le mot de passe"> - <b-input type="password" v-model="passwordConfirm" password-reveal placeholder="mot de passe" required/> + <b-field label="Confirm password"> + <b-input type="password" v-model="passwordConfirm" password-reveal placeholder="password" required/> </b-field> </section> <footer class="modal-card-foot"> - <button class="button is-primary">Créer un compte</button> + <button class="button is-primary">Make account</button> </footer> </div> </form> diff --git a/src/components/TagEditor.vue b/src/components/TagEditor.vue new file mode 100644 index 0000000000000000000000000000000000000000..6855570885580e45157cb74a730ca85222e0fd07 --- /dev/null +++ b/src/components/TagEditor.vue @@ -0,0 +1,144 @@ +<template> + <div class="tagEditor"> + <b-field position="is-centered" has-addons> + <b-autocomplete + v-model="tagName" + :open-on-focus="true" + :data="filteredTags" + icon-right="close-circle" + icon-right-clickable> + <template slot="header"> + <a @click="addNewTagDialog"> + <span> Add new... </span> + </a> + </template> + </b-autocomplete> + <p class="control"> + <b-select placeholder="Select a category" v-model="selectedCategory"> + <option v-for="tag in tagList" v-bind:key="tag.type" :value="tag.name">{{tag.name}}</option> + </b-select> + </p> + <p class="control"> + <b-button class="control" @click="addTagToModel">Add</b-button> + </p> + </b-field> + + <b-taglist> + <b-tag + v-for="tag in tags" + v-bind:key="tag.type" + type="is-info" + @close="removeTagFromModel(tag)" + closable> + {{tag.type}} + </b-tag> + </b-taglist> + </div> +</template> + +<script> +export default { + name: 'TagEditor', + props: { + tags: { + type: Array, + default: function () { + return [] + } + } + }, + data () { + return { + tagName: '', + selectedCategory: null, + tagList: [] + } + }, + computed: { + filteredTags () { + if (this.selectedCategory === null) return [] + console.log('SELECT ' + this.selectedCategory) + var category = this.tagList.filter((tag) => { + return tag.name === this.selectedCategory + }) + console.log('SELECT ' + category) + return category[0].types.filter((type) => { + return type + .toString() + .toLowerCase() + .indexOf(this.tagName.toLowerCase()) >= 0 + }) + } + }, + async mounted () { + this.tagList = await this.getTags() + }, + methods: { + async getTags () { + const url = this.$serverurl + 'models/tags' + const response = await fetch(url) + try { + return await response.json() + } catch (error) { + return null + } + }, + addNewTagDialog () { + this.$buefy.dialog.prompt({ + message: 'Tag', + inputAttrs: { + maxlength: 20, + value: this.name + }, + confirmText: 'Add', + onConfirm: (value) => { + this.addNewTag(value) + var category = this.tagList.filter((tag) => { + return tag.name === this.selectedCategory + }) + category[0].types.push(value) + } + }) + }, + async addNewTag (tag) { + const url = this.$serverurl + 'models/tags' + try { + const response = await fetch( + url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: this.selectedCategory, + type: tag + }) + } + ) + return await response.json() + } catch (error) { + return null + } + }, + addTagToModel () { + this.tags.push({ + name: this.selectedCategory, + type: this.tagName + }) + this.tagName = '' + }, + removeTagFromModel (tag) { + console.log('REMOVE TAG ' + tag) + const index = this.tags.indexOf(tag) + if (index > -1) { + this.tags.splice(index, 1) + } + } + } +} +</script> + +<style> + +</style> diff --git a/src/main.js b/src/main.js index 2ba6ec9e2e2c259d112f6be15fa1a1baec547835..ab4d3ec04d2b8173a1d693ac8c9fd4fff6800c14 100644 --- a/src/main.js +++ b/src/main.js @@ -8,8 +8,8 @@ import './assets/style.scss' Vue.use(Buefy) Vue.config.productionTip = false -Vue.prototype.$serverurl = 'http://localhost:8181/api/v1/' -Vue.prototype.$isLogged = false +// Vue.prototype.$serverurl = process.env.VUE_APP_SERVER_URL +Vue.prototype.$serverurl = 'http://localhost:8181/v1/' new Vue({ router, diff --git a/src/router/index.js b/src/router/index.js index cdcfc71c6fd74369f463a74bf0ce1bf40f3b4a0c..7c273dd945da3eaeef237f7d0091a894b1105105 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -34,12 +34,18 @@ const routes = [ path: '/modeledit', name: 'ModelEdit', component: () => import('../views/ModelEdit.vue'), - props: true + props: true, + meta: { + requiresAuth: true + } }, { path: '/account', name: 'Account', - component: () => import('../views/Account.vue') + component: () => import('../views/Account.vue'), + meta: { + requiresAuth: true + } } ] @@ -47,4 +53,14 @@ const router = new VueRouter({ routes }) +router.beforeEach((to, from, next) => { + if (to.matched.some(record => record.meta.requiresAuth)) { + if (localStorage.getItem('token') == null) { + next({ path: '/' }) + } else next() + } else { + next() + } +}) + export default router diff --git a/src/views/Account.vue b/src/views/Account.vue index 0718d87ebd23eab8e3521b300bea27ce7458b044..e85efa8f7451b6fcf923c8b857fe2e3f5b9261e8 100644 --- a/src/views/Account.vue +++ b/src/views/Account.vue @@ -1,14 +1,14 @@ <template> <div class="account container"> <div class="box"> - <h1 class="title">Bienvenue {{account.username}}</h1> + <h1 class="title">Welcome {{account.username}}</h1> <div class="buttons"> - <b-button>Ajouter un modèle</b-button> - <b-button>Modifier le compte</b-button> + <b-button>Add new model</b-button> + <b-button>Change account details</b-button> </div> </div> <div class="box"> - <h1 class="title is-4">Mes modèles</h1> + <h1 class="title is-4">My models</h1> <ModelTable/> </div> </div> diff --git a/src/views/Model.vue b/src/views/Model.vue index f7427e02d428b9415b23dd006b95b9c560a6c406..a9b42755b887fa6ceb894d0b319d557e6e592596 100644 --- a/src/views/Model.vue +++ b/src/views/Model.vue @@ -16,15 +16,15 @@ </div> <div class="column"> <h1 class="title is-4">{{model.vote}} votes <b-button size="is-small" icon-left="heart-outline" style="top:-2px"/></h1> - <small>Auteur: </small><strong>{{model.author}}</strong> + <small>Author: </small><strong>{{model.author}}</strong> <br> - <small>Date d'ajout: </small><strong>{{model.addedDate}}</strong> + <small>Added: </small><strong>{{new Date(model.addedDate).toLocaleDateString()}}</strong> <br> - <small>Dernière modification: </small><strong>{{model.modificationDate}}</strong> + <small>Last modification: </small><strong>{{new Date(model.modificationDate).toLocaleDateString()}}</strong> <br> <br> <b-button type="is-primary" size="is-medium" icon-left="download"> - Télécharger + Download </b-button> </div> </div> diff --git a/src/views/ModelEdit.vue b/src/views/ModelEdit.vue index 645179bd6b33cb145fc7029efa794a83a3b382a9..16df9c8b9580e333037482897da45e9ec25d8593 100644 --- a/src/views/ModelEdit.vue +++ b/src/views/ModelEdit.vue @@ -1,32 +1,61 @@ <template> <div class="modelEdit container"> <div class="box"> - <b-field label="Nom du modèle"> - <b-input maxlength="30" :value="model.name"/> + <b-field label="Model name"> + <b-input maxlength="30" :v-model="model.name"/> </b-field> - <b-field label="Description courte"> - <b-input maxlength="200" type="textarea" :value="model.shortDescription"/> + <h1 class="title">Description</h1> + <b-field label="Short description"> + <b-input maxlength="200" type="textarea" :v-model="model.shortDescription"/> </b-field> - <b-field label="Description longue"> - <b-input type="textarea" :value="model.longDescription"/> + <b-field label="Long description"> + <markdownEditor v-bind:input="model.longDescription"/> </b-field> + + <h1 class="title">Tags</h1> + <tagEditor v-bind:input="model.tags"/> + + <h1 class="title">Files</h1> + + <h2 class="title is-6">Model</h2> + <b-field position="is-centered"> + <b-upload v-model="dropFiles" multiple drag-drop> + <section class="section"> + <div class="content has-text-centered"> + <p><b-icon icon="upload" size="is-large"/></p> + <p>Drop your files here or click to upload</p> + </div> + </section> + </b-upload> + </b-field> + + <h2 class="title is-6">Custom layers</h2> + <layersEditor/> </div> <div class="box"> - <b-button type="is-warning">Annuler</b-button> - <b-button type="is-danger">Supprimer</b-button> - <b-button type="is-success">Enregistrer</b-button> + <b-button type="is-warning">Abort</b-button> + <b-button type="is-success">Save</b-button> </div> </div> </template> <script> +import markdownEditor from '@/components/MarkdownEditor.vue' +import tagEditor from '@/components/TagEditor.vue' +import layersEditor from '@/components/LayersEditor.vue' + export default { name: 'ModelEdit', - props: ['model'], + components: { + markdownEditor, + tagEditor, + layersEditor + }, data () { return { + model: '', name: '', shortDesc: '', longDesc: '', diff --git a/src/views/Search.vue b/src/views/Search.vue index 07be994e3ce3a1c08c684417c8ff6e450c7d79eb..0010425b9efd1bb08971fa0929b7add90ce8aa63 100644 --- a/src/views/Search.vue +++ b/src/views/Search.vue @@ -6,7 +6,7 @@ <Filters v-bind:tags="tags" v-on:setTags="setTags"/> </div> <div v-if="result" class="column is-four-fifths"> - <h1 class="title is-6">{{result.total}} résultats - page {{result.page}}</h1> + <h1 class="title is-6">{{result.total}} result - page {{result.page}}</h1> <div v-for="model in result.models" v-bind:key="model.name"> <ModelCard v-bind:model="model"/> </div> @@ -58,7 +58,7 @@ export default { this.result = await this.getResult() }, async getTags () { - const url = this.$serverurl + 'tags' + const url = this.$serverurl + 'models/tags' const response = await fetch(url) try { return await response.json()