UI updates; added delete

This commit is contained in:
Dan Elbert 2018-05-01 10:55:57 -05:00
parent c3dbefbb4f
commit 3c87ee8083
14 changed files with 197 additions and 27 deletions

View File

@ -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

View 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>

View File

@ -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() {

View File

@ -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%;

View File

@ -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>
<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> <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>

View File

@ -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">&nbsp;</span> <span class="label is-small-mobile" v-if="showLabels">&nbsp;</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>

View File

@ -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() {

View File

@ -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; })
); );
}, },

View File

@ -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 @@
} }
}, },
getList: debounce(function() { deleteRecipe(recipe) {
this.recipeForDeletion = recipe;
},
recipeDeleteConfirm() {
if (this.recipeForDeletion !== null) {
this.loadResource( this.loadResource(
api.deleteRecipe(this.recipeForDeletion.id).then(() => {
this.recipeForDeletion = null;
return this.getList();
})
);
}
},
recipeDeleteCancel() {
this.recipeForDeletion = null;
},
getList: debounce(function() {
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}),

View File

@ -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);

View File

@ -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));

View File

@ -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);

View 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;
}
}
}

View File

@ -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%;