UI updates; added delete
This commit is contained in:
parent
c3dbefbb4f
commit
3c87ee8083
@ -74,12 +74,9 @@ class RecipesController < ApplicationController
|
|||||||
def destroy
|
def destroy
|
||||||
ensure_owner(@recipe) do
|
ensure_owner(@recipe) do
|
||||||
@recipe.deleted = true
|
@recipe.deleted = true
|
||||||
|
@recipe.save!(validate: false)
|
||||||
|
|
||||||
if @recipe.save(validate: false)
|
render json: { success: true }
|
||||||
redirect_to recipes_url, notice: 'Recipe was successfully destroyed.'
|
|
||||||
else
|
|
||||||
redirect_to recipes_url, error: 'Recipe could not be destroyed.'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
47
app/javascript/components/AppConfirm.vue
Normal file
47
app/javascript/components/AppConfirm.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<app-modal :open="open" :title="message" @dismiss="runCancel">
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="button" class="button is-primary" @click="runConfirm">OK</button>
|
||||||
|
<button type="button" class="button" @click="runCancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</app-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
cancel: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
confirm: {
|
||||||
|
type: Function,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'Are you sure?'
|
||||||
|
},
|
||||||
|
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
runConfirm() {
|
||||||
|
this.confirm();
|
||||||
|
},
|
||||||
|
|
||||||
|
runCancel() {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="container">
|
<div ref="container">
|
||||||
<div ref="modal" :class="['popup', 'modal', { 'is-active': open && error === null }]">
|
<div ref="modal" :class="['popup', 'modal', { 'is-wide': wide, 'is-active': open && error === null }]">
|
||||||
<div class="modal-background" @click="close"></div>
|
<div class="modal-background" @click="close"></div>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
@ -28,7 +28,11 @@
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
title: String
|
title: String,
|
||||||
|
wide: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -120,9 +120,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.bulk-input {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.directions-input {
|
.directions-input {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="button is-primary" @click="bulkEditIngredients">Bulk Edit</button>
|
<button type="button" class="button is-primary" @click="bulkEditIngredients">Bulk Edit</button>
|
||||||
<app-modal :open="isBulkEditing" title="Edit Ingredients" @dismiss="cancelBulkEditing">
|
<app-modal wide :open="isBulkEditing" title="Edit Ingredients" @dismiss="cancelBulkEditing">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half">
|
<div class="column is-half bulk-input">
|
||||||
<textarea ref="bulkEditTextarea" class="textarea is-size-7-mobile bulk-input" v-model="bulkEditText"></textarea>
|
<textarea ref="bulkEditTextarea" class="textarea is-size-7-mobile" v-model="bulkEditText"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<table class="table is-bordered is-narrow is-size-7">
|
<table class="table is-bordered is-narrow is-size-7">
|
||||||
@ -27,7 +27,9 @@
|
|||||||
<button class="button is-secondary" type="button" @click="cancelBulkEditing">Cancel</button>
|
<button class="button is-secondary" type="button" @click="cancelBulkEditing">Cancel</button>
|
||||||
</app-modal>
|
</app-modal>
|
||||||
|
|
||||||
<recipe-edit-ingredient-item v-for="(i, idx) in visibleIngredients" :key="i.id" :ingredient="i" :show-labels="idx === 0 || isMobile" @deleteIngredient="deleteIngredient"></recipe-edit-ingredient-item>
|
<div>
|
||||||
|
<recipe-edit-ingredient-item v-for="(i, idx) in visibleIngredients" :key="i.id" :ingredient="i" :show-labels="idx === 0 || isMobile" @deleteIngredient="deleteIngredient"></recipe-edit-ingredient-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" class="button is-primary" @click="addIngredient">Add Ingredient</button>
|
<button type="button" class="button is-primary" @click="addIngredient">Add Ingredient</button>
|
||||||
</div>
|
</div>
|
||||||
@ -63,7 +65,7 @@
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = /^(?:([\d\/.]+(?:\s+[\d\/]+)?)\s+)?(?:([\w-]+)(?:\s+of)?\s+)?([^,|]+?|.+\|)(?:,\s*([^|]*?))?(?:\s*\[(\d+)\]\s*)?$/i;
|
const regex = /^\s*(?:([\d\/.]+(?:\s+[\d\/]+)?)\s+)?(?:([\w-]+)(?:\s+of)?\s+)?([^,|]+?|.+\|)(?:,\s*([^|]*?))?(?:\s*\[(\d+)\]\s*)?$/i;
|
||||||
|
|
||||||
const magicFunc = function(str) {
|
const magicFunc = function(str) {
|
||||||
if (str === "-") {
|
if (str === "-") {
|
||||||
@ -201,6 +203,12 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.bulk-input {
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile edit-ingredient-item">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
|
|
||||||
<div class="columns is-multiline is-mobile">
|
<div class="columns is-multiline is-mobile">
|
||||||
@ -37,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<span class="label is-small-mobile" v-if="showLabels"> </span>
|
<span class="label is-small-mobile" v-if="showLabels"> </span>
|
||||||
<button type="button" class="button is-danger is-small-mobile" @click="deleteIngredient(ingredient)">
|
<button type="button" class="button is-danger is-small" @click="deleteIngredient(ingredient)">
|
||||||
<app-icon icon="x"></app-icon>
|
<app-icon icon="x" size="md"></app-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -99,4 +99,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
@import "../styles/variables";
|
||||||
|
|
||||||
|
.edit-ingredient-item {
|
||||||
|
border-bottom: solid 1px $grey-light;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
@ -38,7 +38,7 @@
|
|||||||
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_ingredient', params: { id: i.id } }">
|
<router-link v-if="isLoggedIn" class="button" :to="{name: 'edit_ingredient', params: { id: i.id } }">
|
||||||
<app-icon icon="pencil"></app-icon>
|
<app-icon icon="pencil"></app-icon>
|
||||||
</router-link>
|
</router-link>
|
||||||
<button v-if="isLoggedIn" type="button" class="button is-danger">
|
<button v-if="isLoggedIn" type="button" class="button is-danger" @click="deleteIngredient(i)">
|
||||||
<app-icon icon="x"></app-icon>
|
<app-icon icon="x"></app-icon>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@ -52,6 +52,8 @@
|
|||||||
<router-link v-if="isLoggedIn" :to="{name: 'new_ingredient'}" class="button is-primary">Create Ingredient</router-link>
|
<router-link v-if="isLoggedIn" :to="{name: 'new_ingredient'}" class="button is-primary">Create Ingredient</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<app-confirm :open="showConfirmIngredientDelete" :message="confirmIngredientDeleteMessage" :cancel="ingredientDeleteCancel" :confirm="ingredientDeleteConfirm"></app-confirm>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -64,6 +66,7 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ingredientData: null,
|
ingredientData: null,
|
||||||
|
ingredientForDeletion: null,
|
||||||
search: {
|
search: {
|
||||||
page: 1,
|
page: 1,
|
||||||
per: 25,
|
per: 25,
|
||||||
@ -93,6 +96,18 @@
|
|||||||
return this.ingredientData.current_page
|
return this.ingredientData.current_page
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
showConfirmIngredientDelete() {
|
||||||
|
return this.ingredientForDeletion !== null;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmIngredientDeleteMessage() {
|
||||||
|
if (this.ingredientForDeletion !== null) {
|
||||||
|
return `Are you sure you want to delete ${this.ingredientForDeletion.name}?`;
|
||||||
|
} else {
|
||||||
|
return "??";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -102,11 +117,33 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
getList: debounce(function() {
|
getList: debounce(function() {
|
||||||
this.loadResource(
|
return this.loadResource(
|
||||||
api.getIngredientList(this.search.page, this.search.per, this.search.name)
|
api.getIngredientList(this.search.page, this.search.per, this.search.name)
|
||||||
.then(data => this.ingredientData = data)
|
.then(data => this.ingredientData = data)
|
||||||
);
|
);
|
||||||
}, 500, {leading: true, trailing: true})
|
}, 500, {leading: true, trailing: true}),
|
||||||
|
|
||||||
|
deleteIngredient(ingredient) {
|
||||||
|
this.ingredientForDeletion = ingredient;
|
||||||
|
},
|
||||||
|
|
||||||
|
ingredientDeleteCancel() {
|
||||||
|
this.ingredientForDeletion = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
ingredientDeleteConfirm() {
|
||||||
|
if (this.ingredientForDeletion !== null) {
|
||||||
|
this.loadResource(
|
||||||
|
api.deleteIngredient(this.ingredientForDeletion.id).then(res => {
|
||||||
|
this.ingredientForDeletion = null;
|
||||||
|
return this.getList();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("This is where the thing happens!!");
|
||||||
|
this.ingredientForDeletion = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loadResource(
|
this.loadResource(
|
||||||
api.getRecipe(this.recipeId, data => { this.recipe = data; return data; })
|
api.getRecipe(this.recipeId, null, null, null, data => { this.recipe = data; return data; })
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<router-link v-if="isLoggedIn" :to="{name: 'edit_recipe', params: { id: r.id } }" class="button is-primary">
|
<router-link v-if="isLoggedIn" :to="{name: 'edit_recipe', params: { id: r.id } }" class="button is-primary">
|
||||||
<app-icon icon="pencil" size="md"></app-icon>
|
<app-icon icon="pencil" size="md"></app-icon>
|
||||||
</router-link>
|
</router-link>
|
||||||
<button v-if="isLoggedIn" type="button" class="button is-danger">
|
<button v-if="isLoggedIn" type="button" class="button is-danger" @click="deleteRecipe(r)">
|
||||||
<app-icon icon="x" size="md"></app-icon>
|
<app-icon icon="x" size="md"></app-icon>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@ -68,6 +68,8 @@
|
|||||||
|
|
||||||
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager>
|
<app-pager :current-page="currentPage" :total-pages="totalPages" paged-item-name="recipe" @changePage="changePage"></app-pager>
|
||||||
|
|
||||||
|
<app-confirm :open="showConfirmRecipeDelete" :message="confirmRecipeDeleteMessage" :cancel="recipeDeleteCancel" :confirm="recipeDeleteConfirm"></app-confirm>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -80,6 +82,7 @@
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
recipeData: null,
|
recipeData: null,
|
||||||
|
recipeForDeletion: null,
|
||||||
search: {
|
search: {
|
||||||
sortColumn: 'created_at',
|
sortColumn: 'created_at',
|
||||||
sortDirection: 'desc',
|
sortDirection: 'desc',
|
||||||
@ -123,6 +126,18 @@
|
|||||||
return this.recipeData.current_page;
|
return this.recipeData.current_page;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
showConfirmRecipeDelete() {
|
||||||
|
return this.recipeForDeletion !== null;
|
||||||
|
},
|
||||||
|
|
||||||
|
confirmRecipeDeleteMessage() {
|
||||||
|
if (this.showConfirmRecipeDelete) {
|
||||||
|
return `Are you sure you want to delete ${this.recipeForDeletion.name}?`;
|
||||||
|
} else {
|
||||||
|
return "??";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -140,8 +155,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteRecipe(recipe) {
|
||||||
|
this.recipeForDeletion = recipe;
|
||||||
|
},
|
||||||
|
|
||||||
|
recipeDeleteConfirm() {
|
||||||
|
if (this.recipeForDeletion !== null) {
|
||||||
|
this.loadResource(
|
||||||
|
api.deleteRecipe(this.recipeForDeletion.id).then(() => {
|
||||||
|
this.recipeForDeletion = null;
|
||||||
|
return this.getList();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
recipeDeleteCancel() {
|
||||||
|
this.recipeForDeletion = null;
|
||||||
|
},
|
||||||
|
|
||||||
getList: debounce(function() {
|
getList: debounce(function() {
|
||||||
this.loadResource(
|
return this.loadResource(
|
||||||
api.getRecipeList(this.search.page, this.search.per, this.search.sortColumn, this.search.sortDirection, this.search.name, this.search.tags, data => this.recipeData = data)
|
api.getRecipeList(this.search.page, this.search.per, this.search.sortColumn, this.search.sortDirection, this.search.name, this.search.tags, data => this.recipeData = data)
|
||||||
);
|
);
|
||||||
}, 500, {leading: true, trailing: true}),
|
}, 500, {leading: true, trailing: true}),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<button class="button" type="button" @click="showLogin = true">
|
<button class="button" type="button" @click="openDialog">
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -16,7 +16,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Username</label>
|
<label class="label">Username</label>
|
||||||
<div class="control has-icons-left">
|
<div class="control has-icons-left">
|
||||||
<input class="input" type="text" placeholder="username" v-model="username">
|
<input class="input" type="text" placeholder="username" ref="usernameInput" v-model="username">
|
||||||
<app-icon icon="person" size="sm" class="is-left"></app-icon>
|
<app-icon icon="person" size="sm" class="is-left"></app-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -71,8 +71,13 @@
|
|||||||
'setUser'
|
'setUser'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
openDialog() {
|
||||||
|
this.showLogin = true;
|
||||||
|
this.$nextTick(() => this.$refs.usernameInput.focus());
|
||||||
|
},
|
||||||
|
|
||||||
login() {
|
login() {
|
||||||
if (this.username !== '' && this.password != '') {
|
if (this.username !== '' && this.password !== '') {
|
||||||
this.loadResource(api.postLogin(this.username, this.password).then(data => {
|
this.loadResource(api.postLogin(this.username, this.password).then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.setUser(data.user);
|
this.setUser(data.user);
|
||||||
|
@ -187,6 +187,10 @@ class Api {
|
|||||||
return this.post("/recipes/", this.buildRecipeParams(recipe));
|
return this.post("/recipes/", this.buildRecipeParams(recipe));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteRecipe(id) {
|
||||||
|
return this.del("/recipes/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
postPreviewSteps(step_text) {
|
postPreviewSteps(step_text) {
|
||||||
const params = {
|
const params = {
|
||||||
step_text: step_text
|
step_text: step_text
|
||||||
@ -284,6 +288,10 @@ class Api {
|
|||||||
return this.patch("/ingredients/" + ingredient.id, this.buildIngredientParams(ingredient));
|
return this.patch("/ingredients/" + ingredient.id, this.buildIngredientParams(ingredient));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteIngredient(id) {
|
||||||
|
return this.del("/ingredients/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
postIngredientSelectNdbn(ingredient) {
|
postIngredientSelectNdbn(ingredient) {
|
||||||
const url = ingredient.id ? "/ingredients/" + ingredient.id + "/select_ndbn" : "/ingredients/select_ndbn";
|
const url = ingredient.id ? "/ingredients/" + ingredient.id + "/select_ndbn" : "/ingredients/select_ndbn";
|
||||||
return this.post(url, this.buildIngredientParams(ingredient));
|
return this.post(url, this.buildIngredientParams(ingredient));
|
||||||
|
@ -11,6 +11,7 @@ import '../lib/GlobalMixins';
|
|||||||
import App from '../components/App';
|
import App from '../components/App';
|
||||||
|
|
||||||
import AppAutocomplete from "../components/AppAutocomplete";
|
import AppAutocomplete from "../components/AppAutocomplete";
|
||||||
|
import AppConfirm from "../components/AppConfirm";
|
||||||
import AppDateTime from "../components/AppDateTime";
|
import AppDateTime from "../components/AppDateTime";
|
||||||
import AppDatePicker from "../components/AppDatePicker";
|
import AppDatePicker from "../components/AppDatePicker";
|
||||||
import AppIcon from "../components/AppIcon";
|
import AppIcon from "../components/AppIcon";
|
||||||
@ -22,6 +23,7 @@ import AppTagEditor from "../components/AppTagEditor";
|
|||||||
import AppTextField from "../components/AppTextField";
|
import AppTextField from "../components/AppTextField";
|
||||||
|
|
||||||
Vue.component("AppAutocomplete", AppAutocomplete);
|
Vue.component("AppAutocomplete", AppAutocomplete);
|
||||||
|
Vue.component("AppConfirm", AppConfirm);
|
||||||
Vue.component("AppDateTime", AppDateTime);
|
Vue.component("AppDateTime", AppDateTime);
|
||||||
Vue.component("AppDatePicker", AppDatePicker);
|
Vue.component("AppDatePicker", AppDatePicker);
|
||||||
Vue.component("AppIcon", AppIcon);
|
Vue.component("AppIcon", AppIcon);
|
||||||
|
18
app/javascript/styles/_wide_modal.scss
Normal file
18
app/javascript/styles/_wide_modal.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
@include until($desktop) {
|
||||||
|
.modal.is-wide {
|
||||||
|
.modal-content, .modal-card {
|
||||||
|
margin: 0 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include from($desktop) {
|
||||||
|
.modal.is-wide {
|
||||||
|
.modal-content, .modal-card {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 1000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
@import "~bulma/sass/layout/section";
|
@import "~bulma/sass/layout/section";
|
||||||
|
|
||||||
@import "./responsive_controls";
|
@import "./responsive_controls";
|
||||||
|
@import "./wide_modal";
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
Loading…
Reference in New Issue
Block a user