<template> <div class="project-creation-window"> <div class="content"> <h1 class="headline">Create Project</h1> <text-input placeholder="Name" v-model="name" :error="nameError" @clearError="nameError = false"> Name </text-input> <textarea-input placeholder="Description" v-model="description" :error="descriptionError" @clearError="descriptionError = false"> Description </textarea-input> <select-input :values="availableModels" v-model="model"> Model </select-input> <model-abilities :model="currentModel"/> <select-input :values="availableLabels" v-model="label"> Label Provider </select-input> <text-input placeholder="path to folder (leave empty to disable)" v-model="external" @change="checkDirectory"> External Storage </text-input> <div class="external"> <template v-if="externalPathInformation !== null"> <template v-if="externalPathInformation.exists"> The given directory contains {{ externalPathInformation.count }} files.<br> </template> <template v-else> The given directory is not valid.<br> </template> </template> The external storage mode disables file uploads and loads them from the given directory instead. </div> </div> <button-row class="footer"> <button-input @click="createProject" type="primary" :disabled="externalPathInformation !== null && !externalPathInformation.exists"> Create Project </button-input> <button-input @click="$emit('cancel', null)"> Cancel </button-input> </button-row> </div> </template> <script> import TextInput from "@/components/base/text-input"; import TextareaInput from "@/components/base/textarea-input"; import SelectInput from "@/components/base/select-input"; import ButtonInput from "@/components/base/button-input"; import ButtonRow from "@/components/base/button-row"; import ModelAbilities from "@/components/models/model-abilities"; export default { name: "project-creation-window", components: {ModelAbilities, ButtonRow, ButtonInput, SelectInput, TextareaInput, TextInput}, props: ['status', 'socket'], created: function () { // get data this.getModels(); this.getLabels(); // subscribe to changes this.$root.socket.on('connect', this.getModels); this.$root.socket.on('create-model', this.addModel); this.$root.socket.on('remove-model', this.removeModel); this.$root.socket.on('connect', this.getLabels); }, destroyed: function () { this.$root.socket.off('connect', this.getModels); this.$root.socket.off('create-model', this.addModel); this.$root.socket.off('remove-model', this.removeModel); this.$root.socket.off('connect', this.getLabels); }, data: function () { return { name: '', nameError: false, description: '', descriptionError: false, models: [], model: null, labels: [], label: null, external: '', externalPathInformation: null } }, methods: { getModels: function () { this.models = []; this.$root.socket.get('/models') .then(response => response.json()) .then(models => models.forEach(this.addModel)); }, addModel: function (model) { for (let m of this.models) if (m.identifier === model.identifier) return; this.models.push(model); }, removeModel: function (model) { for (let i = 0; i < this.models.length; i++) { if (this.models[i].identifier === model.identifier) { this.models.splice(i, 1); break; } } if (model.identifier === parseInt(this.model) && this.models.length > 0) { this.model = this.models[0].identifier; } }, getLabels: function () { this.$root.socket.get('/label_providers') .then(response => response.json()) .then(labels => this.labels = labels); }, checkDirectory: function (value) { if (!value) { this.externalPathInformation = null; return; } this.$root.socket.post('/folder', { 'folder': this.external }) .then(response => response.json()) .then(data => this.externalPathInformation = data); }, createProject: function () { // check input this.nameError = !this.name; this.descriptionError = !this.description; if (this.nameError || this.descriptionError) return; // create project // TODO then / error this.$root.socket.post('/projects', { name: this.name, description: this.description, model: parseInt(this.model), label: parseInt(this.label), external: this.external === '' ? null : this.external }); // close project creation window this.$emit('cancel', null); } }, computed: { availableModels: function () { return this.models.map(m => { return { name: m.name, value: m.identifier, } }).sort((m1, m2) => m1.name < m2.name ? -1 : +1); }, currentModel: function () { for (let model of this.models) if (model.identifier === parseInt(this.model)) return model; return false; }, availableLabels: function () { let result = []; // add label providers for (let label of this.labels) { result.push({ name: label.name, value: label.identifier, }); } // sort result.sort((a, b) => { if (a.name.includes(b.name)) return +1; if (b.name.includes(a.name)) return -1; if (a.name > b.name) return +1; else return -1; }); // add `None` option result.unshift({ name: 'None', value: null }); return result; } }, watch: { availableModels: function (newValue) { if (this.model === null && newValue.length > 0) { this.model = newValue[0].value; } }, availableLabels: function (newValue) { if (this.label === null && newValue.length > 0) { this.label = newValue[0].value; } }, } } </script> <style scoped> .project-creation-window { display: flex; flex-direction: column; width: 100%; height: 100%; } .content { overflow: auto; padding: 1rem; height: 100%; flex-grow: 1; } .content > :not(h1) { margin-bottom: 0.6rem; } /deep/ .content textarea { height: 4rem; } .model-abilities { font-size: 80%; } .external { font-size: 80%; } .footer { flex-grow: 0; padding: 1rem; display: flex; flex-direction: row; justify-content: flex-end; } </style>