Skip to content

Commit

Permalink
Merge pull request #402 from seyfeb/feature/addedMultiselectToRecipeEdit
Browse files Browse the repository at this point in the history
Added multiselection for categories and keywords to recipe editor
  • Loading branch information
christianlupus authored Dec 21, 2020
2 parents 66f8023 + 9934bc2 commit 5f0cbbb
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
[#390](https://github.com/nextcloud/cookbook/pull/390) @christianlupus
- Automatic deployment of new releases to the nextcloud app store
[#433](https://github.com/nextcloud/cookbook/pull/433) @christianlupus
- Category and keyword selection from list of existing ones in recipe editor
[#402](https://github.com/nextcloud/cookbook/pull/402/) @seyfeb

### Changed
- Switch of project ownership to neextcloud organization in GitHub
Expand Down
87 changes: 87 additions & 0 deletions src/components/EditMultiselect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<fieldset>
<label>{{ fieldLabel }}</label>
<Multiselect
class="edit-multiselect"
v-bind="$attrs"
v-on="$listeners"
/>
</fieldset>
</template>

<script>
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
export default {
name: "EditMultiselect",
components: {
Multiselect
},
props: {
fieldLabel: String,
},
data () {
return {
}
}
}
</script>

<style scoped>
fieldset {
margin-bottom: 1em;
width: 100%;
}
fieldset > * {
margin: 0;
float: left;
}
@media(max-width:1199px) { fieldset > label {
display: block;
float: none;
}}
fieldset > label {
display: inline-block;
width: 10em;
line-height: 18px;
font-weight: bold;
word-spacing: initial;
vertical-align: top;
}
.edit-multiselect {
width: calc(100% - 11em + 10px);
}
@media(max-width:1199px) { .edit-multiselect {
width: 100%;
}}
</style>

<style>
#app
.edit-multiselect
.multiselect__tags {
height: auto;
min-height: 34px;
}
#app
.edit-multiselect
.multiselect__tags
.multiselect__tags-wrap {
display: flex;
flex-wrap: wrap;
padding-bottom: 0px;
}
#app
.edit-multiselect
.multiselect__tags
.multiselect__tags-wrap
.multiselect__tag {
flex-basis: 50px;
margin-bottom: 3px;
}
</style>
96 changes: 94 additions & 2 deletions src/components/RecipeEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<EditTimeField :fieldName="'prepTime'" :fieldLabel="t('cookbook', 'Preparation time')" />
<EditTimeField :fieldName="'cookTime'" :fieldLabel="t('cookbook', 'Cooking time')" />
<EditTimeField :fieldName="'totalTime'" :fieldLabel="t('cookbook', 'Total time')" />
<EditInputField :fieldName="'recipeCategory'" :fieldType="'text'" :fieldLabel="t('cookbook', 'Category')" />
<EditInputField :fieldName="'keywords'" :fieldType="'text'" :fieldLabel="t('cookbook', 'Keywords (comma separated)')" />
<EditMultiselect :fieldLabel="t('cookbook', 'Category')" :placeholder="t('cookbook', 'Choose category')" v-model="recipe['recipeCategory']" :options="allCategories" :taggable="true" :multiple="false" :loading="isFetchingCategories" @tag="addCategory" />
<EditMultiselect :fieldLabel="t('cookbook', 'Keywords')" :placeholder="t('cookbook', 'Choose keywords')" v-model="selectedKeywords" :options="allKeywords" :taggable="true" :multiple="true" :tagWidth="60" :loading="isFetchingKeywords" @tag="addKeyword" />
<EditInputField :fieldName="'recipeYield'" :fieldType="'number'" :fieldLabel="t('cookbook', 'Servings')" />
<EditInputGroup :fieldName="'tool'" :fieldType="'text'" :fieldLabel="t('cookbook', 'Tools')" v-bind:createFieldsOnNewlines="true" />
<EditInputGroup :fieldName="'recipeIngredient'" :fieldType="'text'" :fieldLabel="t('cookbook', 'Ingredients')" v-bind:createFieldsOnNewlines="true" />
Expand All @@ -20,6 +20,7 @@
import EditImageField from './EditImageField'
import EditInputField from './EditInputField'
import EditInputGroup from './EditInputGroup'
import EditMultiselect from './EditMultiselect'
import EditTimeField from './EditTimeField'
export default {
Expand All @@ -28,6 +29,7 @@ export default {
EditImageField,
EditInputField,
EditInputGroup,
EditMultiselect,
EditTimeField,
},
props: ['id'],
Expand Down Expand Up @@ -57,6 +59,11 @@ export default {
prepTime: [0, 0],
cookTime: [0, 0],
totalTime: [0, 0],
allCategories: [],
isFetchingCategories: true,
isFetchingKeywords: true,
allKeywords: [],
selectedKeywords: [],
}
},
watch: {
Expand All @@ -75,14 +82,83 @@ export default {
let mins = this.totalTime[1].toString().padStart(2, '0')
this.recipe.totalTime = 'PT' + hours + 'H' + mins + 'M'
},
selectedKeywords: {
deep: true,
handler() {
// convert keyword array to comma-separated string
this.recipe['keywords'] = this.selectedKeywords.join()
}
}
},
methods: {
/**
* Add newly created category and set as selected.
*/
addCategory (newCategory) {
this.allCategories.push(newCategory)
this.recipe['recipeCategory'] = newCategory
},
/**
* Add newly created keyword.
*/
addKeyword (newKeyword) {
this.allKeywords.push(newKeyword)
this.selectedKeywords.push(newKeyword)
},
addEntry: function(field, index, content='') {
this.recipe[field].splice(index, 0, content)
},
deleteEntry: function(field, index) {
this.recipe[field].splice(index, 1)
},
/**
* Fetch and display recipe categories
*/
fetchCategories: function() {
$.get(this.$window.baseUrl + '/categories').done((json) => {
json = json || []
this.allCategories = []
for (let i=0; i<json.length; i++) {
if (json[i].name != '*') {
this.allCategories.push(
json[i].name,
)
}
}
this.isFetchingCategories = false
})
.fail((e) => {
alert(t('cookbook', 'Failed to fetch categories'))
if (e && e instanceof Error) {
throw e
}
})
},
/**
* Fetch and display recipe keywords
*/
fetchKeywords: function() {
$.ajax(this.$window.baseUrl + '/keywords').done((json) => {
json = json || []
if (json) {
this.allKeywords = []
for (let i=0; i<json.length; i++) {
if (json[i].name != '*') {
this.allKeywords.push(
json[i].name,
)
}
}
}
this.isFetchingKeywords = false
})
.fail((e) => {
alert(t('cookbook', 'Failed to fetch keywords'))
if (e && e instanceof Error) {
throw e
}
})
},
loadRecipeData: function() {
if (!this.$store.state.recipe) {
// Make the control row show that a recipe is loading
Expand Down Expand Up @@ -171,6 +247,8 @@ export default {
}
},
setup: function() {
this.fetchCategories()
this.fetchKeywords()
if (this.$route.params.id) {
// Load the recipe from store and make edits to a local copy first
this.recipe = { ...this.$store.state.recipe }
Expand All @@ -187,6 +265,20 @@ export default {
if (timeComps) {
this.totalTime = [timeComps[1], timeComps[2]]
}
this.selectedKeywords = this.recipe['keywords'].split(',')
// fallback if fetching all keywords fails
this.selectedKeywords.forEach(kw => {
if (!this.allKeywords.includes(kw)) {
this.allKeywords.push(kw)
}
})
// fallback if fetching all categories fails
if (!this.allCategories.includes(this.recipe['recipeCategory'])) {
this.allCategories.push(this.recipe['recipeCategory'])
}
// Always set the active page last!
this.$store.dispatch('setPage', { page: 'edit' })
} else {
Expand Down

0 comments on commit 5f0cbbb

Please sign in to comment.