Continue converting to composition api

This commit is contained in:
Dan Elbert 2024-10-01 09:32:09 -05:00
parent a071e6b21e
commit 0d35f50dbf
17 changed files with 657 additions and 803 deletions

View File

@ -4,7 +4,7 @@
<script> <script>
import { computed, nextTick, useTemplateRef, watch } from "vue"; import { computed, nextTick, onMounted, onUpdated, useTemplateRef } from "vue";
import Caret from "../iconic/svg/smart/caret"; import Caret from "../iconic/svg/smart/caret";
import Check from "../iconic/svg/smart/check"; import Check from "../iconic/svg/smart/check";
@ -130,14 +130,18 @@
svgElement.value.update(); svgElement.value.update();
} }
watch( function updateScripts() {
() => props.icon,
() => {
ensureSvgApi(svgName.value, svgData.value.scriptBlocks); ensureSvgApi(svgName.value, svgData.value.scriptBlocks);
nextTick(() => setupSvgApi(svgName.value)); setupSvgApi(svgName.value);
}, }
{ immediate: true }
); onMounted(() => {
updateScripts();
});
onUpdated(() => {
updateScripts();
});
return { return {
svgData, svgData,

View File

@ -11,10 +11,13 @@
</nav> </nav>
</template> </template>
<script> <script setup>
export default { import { computed } from "vue";
props: {
const emit = defineEmits(["changePage"]);
const props = defineProps({
pagedItemName: { pagedItemName: {
required: false, required: false,
type: String, type: String,
@ -48,19 +51,18 @@
type: Boolean, type: Boolean,
default: false default: false
} }
}, });
computed: { const pageItems = computed(() => {
pageItems() {
const items = new Set(); const items = new Set();
for (let x = 0; x < this.pageOuterWindow; x++) { for (let x = 0; x < props.pageOuterWindow; x++) {
items.add(x + 1); items.add(x + 1);
items.add(this.totalPages - x); items.add(props.totalPages - x);
} }
const start = this.currentPage - Math.ceil(this.pageWindow / 2); const start = props.currentPage - Math.ceil(props.pageWindow / 2);
const end = this.currentPage + Math.floor(this.pageWindow / 2); const end = props.currentPage + Math.floor(props.pageWindow / 2);
for (let x = start; x <= end; x++) { for (let x = start; x <= end; x++) {
items.add(x); items.add(x);
@ -69,7 +71,7 @@
let emptySpace = -1; let emptySpace = -1;
const finalList = []; const finalList = [];
[...items.values()].filter(p => p > 0 && p <= this.totalPages).sort((a, b) => a - b).forEach((p, idx, list) => { [...items.values()].filter(p => p > 0 && p <= props.totalPages).sort((a, b) => a - b).forEach((p, idx, list) => {
finalList.push(p); finalList.push(p);
if (list[idx + 1] && list[idx + 1] !== p + 1) { if (list[idx + 1] && list[idx + 1] !== p + 1) {
finalList.push(emptySpace--); finalList.push(emptySpace--);
@ -77,30 +79,13 @@
}); });
return finalList; return finalList;
}, });
isLastPage() { const isLastPage = computed(() => props.currentPage === props.totalPages);
return this.currentPage === this.totalPages; const isFirstPage = computed(() => props.currentPage === 1);
},
isFirstPage() { function changePage(idx) {
return this.currentPage === 1; emit("changePage", idx);
} }
},
methods: {
changePage(idx) {
this.$emit("changePage", idx);
}
}
}
</script> </script>
<style lang="scss" scoped>
ul.pagination {
}
</style>

View File

@ -9,11 +9,13 @@
</span> </span>
</template> </template>
<script> <script setup>
import { computed, ref, useTemplateRef } from "vue";
export default { const emit = defineEmits(["update:modelValue"]);
props: {
const props = defineProps({
starCount: { starCount: {
required: false, required: false,
type: Number, type: Number,
@ -37,69 +39,55 @@
type: Number, type: Number,
default: 0 default: 0
} }
}, });
data() { const temporaryValue = ref(null);
return { const ratingPercent = computed(() => ((props.modelValue || 0) / props.starCount) * 100.0);
temporaryValue: null const wrapperEl = useTemplateRef("wrapper");
};
},
computed: { const temporaryPercent = computed(() => {
ratingPercent() { if (temporaryValue.value !== null) {
return ((this.modelValue || 0) / this.starCount) * 100.0; return (temporaryValue.value / props.starCount) * 100.0;
},
temporaryPercent() {
if (this.temporaryValue !== null) {
return (this.temporaryValue / this.starCount) * 100.0;
} else { } else {
return null; return null;
} }
}, });
filledStyle() { const filledStyle = computed(() => {
const width = this.temporaryPercent || this.ratingPercent; const width = temporaryPercent.value || ratingPercent.value;
return { return {
width: width + "%" width: width + "%"
}; };
} });
},
methods: { function handleClick(evt) {
handleClick(evt) { if (temporaryValue.value !== null) {
if (this.temporaryValue !== null) { emit("update:modelValue", temporaryValue.value);
this.$emit("update:modelValue", this.temporaryValue);
} }
}, }
handleMousemove(evt) { function handleMousemove(evt) {
if (this.readonly) { if (props.readonly) {
return; return;
} }
const wrapperBox = this.$refs.wrapper.getBoundingClientRect(); const wrapperBox = wrapperEl.value.getBoundingClientRect();
const wrapperWidth = wrapperBox.right - wrapperBox.left; const wrapperWidth = wrapperBox.right - wrapperBox.left;
const mousePosition = evt.clientX; const mousePosition = evt.clientX;
if (mousePosition > wrapperBox.left && mousePosition < wrapperBox.right) { if (mousePosition > wrapperBox.left && mousePosition < wrapperBox.right) {
const filledRatio = ((mousePosition - wrapperBox.left) / wrapperWidth); const filledRatio = ((mousePosition - wrapperBox.left) / wrapperWidth);
const totalSteps = this.starCount / this.step; const totalSteps = props.starCount / props.step;
const filledSteps = Math.round(totalSteps * filledRatio); const filledSteps = Math.round(totalSteps * filledRatio);
this.temporaryValue = filledSteps * this.step; temporaryValue.value = filledSteps * props.step;
} }
}, }
handleMouseleave(evt) { function handleMouseleave(evt) {
this.temporaryValue = null; temporaryValue.value = null;
} }
},
components: {
}
}
</script> </script>

View File

@ -6,12 +6,14 @@
</div> </div>
</template> </template>
<script> <script setup>
import { ref } from "vue";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
export default { const emit = defineEmits(["update:modelValue"]);
props: {
const props = defineProps({
placeholder: { placeholder: {
required: false, required: false,
type: String, type: String,
@ -23,44 +25,26 @@ export default {
type: String, type: String,
default: "" default: ""
} }
});
const text = ref(null);
const triggerInput = debounce(function() {
emit("update:modelValue", text.value);
}, },
250,
{ leading: false, trailing: true })
data() { function userUpdateText(newText) {
return { if (text.value !== newText) {
text: null text.value = newText;
}; triggerInput();
},
computed: {
},
methods: {
triggerInput: debounce(function() {
this.$emit("update:modelValue", this.text);
}, 250, {leading: false, trailing: true}),
userUpdateText(text) {
if (this.text !== text) {
this.text = text;
this.triggerInput();
} }
}, }
propUpdateText(text) { function propUpdateText(newText) {
if (this.text === null && this.text !== text) { if (text.value === null && text.value !== newText) {
this.text = text; text.value = newText;
}
}
},
created() {
this.$watch("modelValue",
val => this.propUpdateText(val),
{
immediate: true
}
);
} }
} }

View File

@ -7,42 +7,33 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import {computed, nextTick, ref, useTemplateRef} from "vue";
props: {
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: { modelValue: {
required: true, required: true,
type: Array type: Array
} }
}, });
data() { const hasFocus = ref(false);
return { const tagText = computed(() => props.modelValue.join(" "));
hasFocus: false const inputElement = useTemplateRef("input");
};
},
computed: { function inputHandler(el) {
tagText() {
return this.modelValue.join(" ");
}
},
watch: {
},
methods: {
inputHandler(el) {
let str = el.target.value; let str = el.target.value;
this.checkInput(str); checkInput(str);
this.$nextTick(() => { nextTick(() => {
el.target.value = str; el.target.value = str;
}); });
}, }
checkInput(str) { function checkInput(str) {
if (this.hasFocus) { if (hasFocus.value) {
const m = str.match(/\S\s+\S*$/); const m = str.match(/\S\s+\S*$/);
if (m !== null) { if (m !== null) {
@ -54,22 +45,21 @@
const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))]; const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))];
if (!this.arraysEqual(newTags, this.modelValue)) { if (!arraysEqual(newTags, props.modelValue)) {
this.$emit("update:modelValue", newTags); emit("update:modelValue", newTags);
} }
}, }
getFocus() { function getFocus() {
this.hasFocus = true; hasFocus.value = true;
}, }
loseFocus() { function loseFocus() {
this.hasFocus = false; hasFocus.value = false;
this.checkInput(this.$refs.input.value); checkInput(inputElement.value.value);
}
}, function arraysEqual(arr1, arr2) {
arraysEqual(arr1, arr2) {
if(arr1.length !== arr2.length) if(arr1.length !== arr2.length)
return false; return false;
for(let i = arr1.length; i--;) { for(let i = arr1.length; i--;) {
@ -78,8 +68,5 @@
} }
return true; return true;
} }
}
}
</script> </script>

View File

@ -2,8 +2,8 @@
<div class="field"> <div class="field">
<label v-if="label.length" class="label is-small-mobile">{{ label }}</label> <label v-if="label.length" class="label is-small-mobile">{{ label }}</label>
<div :class="controlClasses"> <div :class="controlClasses">
<textarea v-if="isTextarea" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled"></textarea> <textarea v-if="isTextarea" :class="inputClasses" v-model="model" :disabled="disabled"></textarea>
<input v-else :type="type" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled"> <input v-else :type="type" :class="inputClasses" v-model="model" :disabled="disabled">
<app-icon class="is-right" icon="warning" v-if="validationError !== null"></app-icon> <app-icon class="is-right" icon="warning" v-if="validationError !== null"></app-icon>
</div> </div>
<p v-if="helpMessage !== null" :class="helpClasses"> <p v-if="helpMessage !== null" :class="helpClasses">
@ -12,10 +12,11 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import { computed } from "vue";
props: {
const props = defineProps({
label: { label: {
required: false, required: false,
type: String, type: String,
@ -40,53 +41,38 @@
type: String, type: String,
default: null default: null
} }
}, });
computed: { const model = defineModel({
isTextarea() { type: [String, Number],
return this.type === "textarea"; default: ""
}, });
controlClasses() { const isTextarea = computed(() => props.type === "textarea");
return [ const controlClasses = computed(() => [
"control", "control",
{ {
"has-icons-right": this.validationError !== null "has-icons-right": props.validationError !== null
} }
] ]);
},
inputClasses() { const inputClasses = computed(() =>[
return [
"is-small-mobile", "is-small-mobile",
{ {
"textarea": this.isTextarea, "textarea": isTextarea.value,
"input": !this.isTextarea, "input": !isTextarea.value,
"is-danger": this.validationError !== null "is-danger": props.validationError !== null
} }
] ]);
},
helpMessage() { const helpMessage = computed(() => props.validationError);
return this.validationError; const helpClasses = computed(() => [
},
helpClasses() {
return [
"help", "help",
{ {
"is-danger": this.validationError !== null "is-danger": props.validationError !== null
} }
]; ]);
}
},
methods: {
input(evt) {
this.$emit("update:modelValue", evt.target.value);
}
}
}
</script> </script>

View File

@ -6,16 +6,14 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { const props = defineProps({
props: {
errors: { errors: {
required: false, required: false,
type: Object, type: Object,
default: {} default: {}
} }
} });
}
</script> </script>

View File

@ -144,14 +144,19 @@
</div> </div>
</template> </template>
<script> <script setup>
import { computed } from "vue";
import api from "../lib/Api"; import api from "../lib/Api";
import { mapState } from "pinia"; import { mapState } from "pinia";
import { useNutrientStore } from "../stores/nutrient"; import { useNutrientStore } from "../stores/nutrient";
import { useLoadResource } from "../lib/useLoadResource";
export default { const nutrientStore = useNutrientStore();
props: { const nutrients = computed(() => nutrientStore.nutrientList);
const { loadResource } = useLoadResource();
const props = defineProps({
food: { food: {
required: true, required: true,
type: Object type: Object
@ -166,53 +171,35 @@
type: String, type: String,
default: "Editing" default: "Editing"
} }
}, });
data() { const visibleFoodUnits = computed(() => props.food.food_units.filter(iu => iu._destroy !== true));
return { const hasNdbn = computed(() => props.food.ndbn !== null);
}; function addUnit() {
}, props.food.food_units.push({
computed: {
...mapState(useNutrientStore, {
nutrients: 'nutrientList'
}),
visibleFoodUnits() {
return this.food.food_units.filter(iu => iu._destroy !== true);
},
hasNdbn() {
return this.food.ndbn !== null;
}
},
methods: {
addUnit() {
this.food.food_units.push({
id: null, id: null,
name: null, name: null,
gram_weight: null gram_weight: null
}); });
}, }
removeUnit(unit) { function removeUnit(unit) {
if (unit.id) { if (unit.id) {
unit._destroy = true; unit._destroy = true;
} else { } else {
const idx = this.food.food_units.findIndex(i => i === unit); const idx = props.food.food_units.findIndex(i => i === unit);
this.food.food_units.splice(idx, 1); props.food.food_units.splice(idx, 1);
}
} }
},
removeNdbn() { function removeNdbn() {
this.food.ndbn = null; props.food.ndbn = null;
this.food.usda_food_name = null; props.food.usda_food_name = null;
this.food.ndbn_units = []; props.food.ndbn_units = [];
}, }
updateSearchItems(text) { function updateSearchItems(text) {
return api.getUsdaFoodSearch(text) return api.getUsdaFoodSearch(text)
.then(data => data.map(f => { .then(data => data.map(f => {
return { return {
@ -221,23 +208,18 @@
description: ["#", f.ndbn, ", Cal:", f.kcal, ", Carbs:", f.carbohydrates, ", Fat:", f.lipid, ", Protein:", f.protein].join("") description: ["#", f.ndbn, ", Cal:", f.kcal, ", Carbs:", f.carbohydrates, ", Fat:", f.lipid, ", Protein:", f.protein].join("")
} }
})); }));
}, }
searchItemSelected(food) { function searchItemSelected(food) {
this.food.ndbn = food.ndbn; props.food.ndbn = food.ndbn;
this.food.usda_food_name = food.name; props.food.usda_food_name = food.name;
this.food.ndbn_units = []; props.food.ndbn_units = [];
this.loadResource( loadResource(
api.postIngredientSelectNdbn(this.food) api.postIngredientSelectNdbn(props.food)
.then(i => Object.assign(this.food, i)) .then(i => Object.assign(props.food, i))
); );
},
},
components: {
}
} }
</script> </script>

View File

@ -53,25 +53,20 @@
</div> </div>
</template> </template>
<script> <script setup>
import { mapState } from "pinia"; import { computed } from "vue";
import { useNutrientStore } from "../stores/nutrient"; import { useNutrientStore } from "../stores/nutrient";
export default { const props = defineProps({
props: {
food: { food: {
required: true, required: true,
type: Object type: Object
} }
}, });
computed: { const nutrientStore = useNutrientStore();
...mapState(useNutrientStore, { const nutrients = computed(() => nutrientStore.nutrientList);
nutrients: 'nutrientList'
}),
}
}
</script> </script>

View File

@ -31,12 +31,11 @@
</template> </template>
<script> <script setup>
import RecipeEdit from "./RecipeEdit"; import RecipeEdit from "./RecipeEdit";
export default { const props = defineProps({
props: {
log: { log: {
required: true, required: true,
type: Object type: Object
@ -46,12 +45,7 @@
type: Object, type: Object,
default: {} default: {}
}, },
}, });
components: {
RecipeEdit
}
}
</script> </script>

View File

@ -44,22 +44,16 @@
</div> </div>
</template> </template>
<script> <script setup>
import RecipeShow from "./RecipeShow"; import RecipeShow from "./RecipeShow";
export default { const props = defineProps({
props: {
log: { log: {
required: true, required: true,
type: Object type: Object
} }
}, });
components: {
RecipeShow
}
}
</script> </script>

View File

@ -19,37 +19,27 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { import { computed } from "vue";
props: {
const emit = defineEmits(["save", "cancel"]);
const props = defineProps({
note: { note: {
required: true, required: true,
type: Object type: Object
} }
}, });
data() { const canSave = computed(() => props.note?.content?.length);
return {
};
},
computed: { function save() {
canSave() { emit("save", props.note);
return this.note && this.note.content && this.note.content.length; }
}
},
methods: { function cancel() {
save() { emit("cancel");
this.$emit("save", this.note); }
},
cancel() {
this.$emit("cancel");
}
}
}
</script> </script>

View File

@ -206,16 +206,15 @@ _underline_
</div> </div>
</template> </template>
<script> <script setup>
import { computed, ref, watch } from "vue";
//import autosize from "autosize"; //import autosize from "autosize";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import api from "../lib/Api"; import api from "../lib/Api";
import RecipeEditIngredientEditor from "./RecipeEditIngredientEditor"; import RecipeEditIngredientEditor from "./RecipeEditIngredientEditor";
export default { const props = defineProps({
props: {
recipe: { recipe: {
required: true, required: true,
type: Object type: Object
@ -225,48 +224,29 @@ _underline_
type: Boolean, type: Boolean,
default: false default: false
} }
}, });
data() { const stepPreviewCache = ref(null);
return { const isDescriptionHelpOpen = ref(false);
stepPreviewCache: null,
isDescriptionHelpOpen: false
};
},
computed: { const stepPreview = computed(() => {
stepPreview() { if (stepPreviewCache.value === null) {
if (this.stepPreviewCache === null) { return props.recipe.rendered_steps;
return this.recipe.rendered_steps;
} else { } else {
return this.stepPreviewCache; return stepPreviewCache.value;
} }
} });
},
methods: { const updatePreview = debounce(function() {
api.postPreviewSteps(props.recipe.step_text)
.then(data => stepPreviewCache.value = data.rendered_steps)
.catch(err => stepPreviewCache.value = "?? Error ??");
}, 750);
updatePreview: debounce(function() { watch(
api.postPreviewSteps(this.recipe.step_text) () => props.recipe.step_text,
.then(data => this.stepPreviewCache = data.rendered_steps) () => updatePreview()
.catch(err => this.stepPreviewCache = "?? Error ??"); );
}, 750)
},
watch: {
'recipe.step_text': function() {
this.updatePreview();
}
},
mounted() {
//autosize(this.$refs.step_text_area);
},
components: {
RecipeEditIngredientEditor
}
}
</script> </script>

View File

@ -2,7 +2,7 @@
<div> <div>
<h1 class="title">Recipes</h1> <h1 class="title">Recipes</h1>
<router-link v-if="isLoggedIn" :to="{name: 'new_recipe'}" class="button is-primary">Create Recipe</router-link> <router-link v-if="appConfig.isLoggedIn" :to="{name: 'new_recipe'}" class="button is-primary">Create Recipe</router-link>
<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>
@ -25,10 +25,10 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<app-search-text placeholder="search names" :value="search.name" @input="setSearchName($event)"></app-search-text> <app-search-text placeholder="search names" :value="search.name" @update:modelValue="setSearchName($event)"></app-search-text>
</td> </td>
<td> <td>
<app-search-text placeholder="search tags" :value="search.tags" @input="setSearchTags($event)"></app-search-text> <app-search-text placeholder="search tags" :value="search.tags" @update:modelValue="setSearchTags($event)"></app-search-text>
</td> </td>
<td colspan="5"></td> <td colspan="5"></td>
</tr> </tr>
@ -49,10 +49,12 @@
<td class="recipe-time">{{ formatRecipeTime(r.total_time, r.active_time) }}</td> <td class="recipe-time">{{ formatRecipeTime(r.total_time, r.active_time) }}</td>
<td><app-date-time :date-time="r.created_at" :show-time="false"></app-date-time></td> <td><app-date-time :date-time="r.created_at" :show-time="false"></app-date-time></td>
<td> <td>
<app-dropdown hover v-if="isLoggedIn" class="is-right"> <app-dropdown hover v-if="appConfig.isLoggedIn" class="is-right">
<button slot="button" class="button is-small"> <template #button>
<button class="button is-small">
<app-icon icon="menu"></app-icon> <app-icon icon="menu"></app-icon>
</button> </button>
</template>
<div class="dropdown-item"> <div class="dropdown-item">
<router-link :to="{name: 'new_log', params: { recipeId: r.id } }" class="button is-primary is-fullwidth"> <router-link :to="{name: 'new_log', params: { recipeId: r.id } }" class="button is-primary is-fullwidth">
@ -88,106 +90,109 @@
</div> </div>
</template> </template>
<script> <script setup>
import { computed, reactive, ref, watch } from "vue";
import { useRouter } from 'vue-router'
import api from "../lib/Api"; import api from "../lib/Api";
import { mapWritableState, mapState } from "pinia";
import AppLoading from "./AppLoading"; import AppLoading from "./AppLoading";
import { useAppConfigStore } from "../stores/appConfig"; import { useAppConfigStore } from "../stores/appConfig";
import { useMediaQueryStore } from "../stores/mediaQuery"; import { useMediaQueryStore } from "../stores/mediaQuery";
import { useLoadResource } from "../lib/useLoadResource";
export default { const appConfig = useAppConfigStore();
props: { const mediaQueries = useMediaQueryStore();
const { loadResource, localLoading } = useLoadResource();
const router = useRouter();
const props = defineProps({
searchQuery: { searchQuery: {
type: Object, type: Object,
required: false, required: false,
default: {} default: {}
} }
}, });
data() { const tableHeader = [
return {
recipeData: null,
recipeForDeletion: null
};
},
computed: {
...mapState(useMediaQueryStore, { isTouch: store => store.touch }),
...mapWritableState(useAppConfigStore, [
"initialLoad"
]),
search() {
return {
name: this.searchQuery.name || null,
tags: this.searchQuery.tags || null,
column: this.searchQuery.column || "created_at",
direction: this.searchQuery.direction || "desc",
page: this.searchQuery.page || 1,
per: this.searchQuery.per || 25
}
},
recipes() {
if (this.recipeData) {
return this.recipeData.recipes;
} else {
return [];
}
},
tableHeader() {
return [
{name: 'name', label: 'Name', sort: true}, {name: 'name', label: 'Name', sort: true},
{name: 'tags', label: 'Tags', sort: false}, {name: 'tags', label: 'Tags', sort: false},
{name: 'rating', label: 'Rating', sort: true}, {name: 'rating', label: 'Rating', sort: true},
{name: 'yields', label: 'Yields', sort: false}, {name: 'yields', label: 'Yields', sort: false},
{name: 'total_time', label: 'Time', sort: true}, {name: 'total_time', label: 'Time', sort: true},
{name: 'created_at', label: 'Created', sort: true} {name: 'created_at', label: 'Created', sort: true}
] ];
},
totalPages() { const recipeData = ref(null);
if (this.recipeData) { const recipeForDeletion = ref(null);
return this.recipeData.total_pages; const isTouch = computed(() => mediaQueries.touch);
const search = computed(() => ({
name: props.searchQuery.name || null,
tags: props.searchQuery.tags || null,
column: props.searchQuery.column || "created_at",
direction: props.searchQuery.direction || "desc",
page: props.searchQuery.page || 1,
per: props.searchQuery.per || 25
}));
const recipes = computed(() => {
if (recipeData.value) {
return recipeData.value.recipes;
} else {
return [];
}
});
const totalPages = computed(() => {
if (recipeData.value) {
return recipeData.value.total_pages;
} }
return 0; return 0;
}, });
currentPage() { const currentPage = computed(() => {
if (this.recipeData) { if (recipeData.value) {
return this.recipeData.current_page; return recipeData.value.current_page;
} }
return 0; return 0;
}, });
showConfirmRecipeDelete() { const showConfirmRecipeDelete = computed(() => recipeForDeletion.value !== null);
return this.recipeForDeletion !== null;
},
confirmRecipeDeleteMessage() { const confirmRecipeDeleteMessage = computed(() => {
if (this.showConfirmRecipeDelete) { if (showConfirmRecipeDelete.value) {
return `Are you sure you want to delete ${this.recipeForDeletion.name}?`; return `Are you sure you want to delete ${recipeForDeletion.value.name}?`;
} else { } else {
return "??"; return "??";
} }
} });
},
methods: { watch(search, () => {
buildQueryParams() { getList().then(() => appConfig.initialLoad = true);
}, {
deep: true,
immediate: true
});
function getList() {
return loadResource(
api.getRecipeList(search.value.page, search.value.per, search.value.column, search.value.direction, search.value.name, search.value.tags, data => recipeData.value = data)
);
}
function buildQueryParams() {
return { return {
name: this.searchQuery.name, name: props.searchQuery.name,
tags: this.searchQuery.tags, tags: props.searchQuery.tags,
column: this.searchQuery.column, column: props.searchQuery.column,
direction: this.searchQuery.direction, direction: props.searchQuery.direction,
page: this.searchQuery.page, page: props.searchQuery.page,
per: this.searchQuery.per per: props.searchQuery.per
}
} }
},
redirectToParams(params) { function redirectToParams(params) {
const rParams = {}; const rParams = {};
if (params.name) { if (params.name) {
@ -214,17 +219,17 @@
rParams.per = params.per; rParams.per = params.per;
} }
this.$router.push({name: 'recipeList', query: rParams}); router.push({name: 'recipeList', query: rParams});
}, }
changePage(idx) { function changePage(idx) {
const p = this.buildQueryParams(); const p = buildQueryParams();
p.page = idx; p.page = idx;
this.redirectToParams(p); redirectToParams(p);
}, }
setSort(col) { function setSort(col) {
const p = this.buildQueryParams(); const p = buildQueryParams();
if (p.column === col) { if (p.column === col) {
p.direction = p.direction === "desc" ? "asc" : "desc"; p.direction = p.direction === "desc" ? "asc" : "desc";
@ -232,53 +237,47 @@
p.column = col; p.column = col;
p.direction = "asc"; p.direction = "asc";
} }
this.redirectToParams(p); redirectToParams(p);
}, }
setSearchName(name) { function setSearchName(name) {
const p = this.buildQueryParams(); const p = buildQueryParams();
if (name !== p.name) { if (name !== p.name) {
p.name = name; p.name = name;
p.page = null; p.page = null;
this.redirectToParams(p); redirectToParams(p);
}
} }
},
setSearchTags(tags) { function setSearchTags(tags) {
const p = this.buildQueryParams(); const p = buildQueryParams();
if (tags !== p.tags) { if (tags !== p.tags) {
p.tags = tags; p.tags = tags;
p.page = null; p.page = null;
this.redirectToParams(p); redirectToParams(p);
}
} }
},
deleteRecipe(recipe) { function deleteRecipe(recipe) {
this.recipeForDeletion = recipe; recipeForDeletion.value = recipe;
}, }
recipeDeleteConfirm() { function recipeDeleteConfirm() {
if (this.recipeForDeletion !== null) { if (recipeForDeletion.value !== null) {
this.loadResource( loadResource(
api.deleteRecipe(this.recipeForDeletion.id).then(() => { api.deleteRecipe(recipeForDeletion.value.id).then(() => {
this.recipeForDeletion = null; recipeForDeletion.value = null;
return this.getList(); return getList();
}) })
); );
} }
}, }
recipeDeleteCancel() { function recipeDeleteCancel() {
this.recipeForDeletion = null; recipeForDeletion.value = null;
}, }
getList() { function formatRecipeTime(total, active) {
return this.loadResource(
api.getRecipeList(this.search.page, this.search.per, this.search.column, this.search.direction, this.search.name, this.search.tags, data => this.recipeData = data)
);
},
formatRecipeTime(total, active) {
let str = ""; let str = "";
if (total && total > 0) { if (total && total > 0) {
@ -295,24 +294,7 @@
return str; return str;
} }
},
created() {
this.$watch("search",
() => {
this.getList().then(() => this.initialLoad = true);
},
{
deep: true,
immediate: true
}
);
},
components: {
AppLoading
}
}
</script> </script>

View File

@ -19,12 +19,14 @@ require "rails/test_unit/railtie"
# you've limited to :test, :development, or :production. # you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
require_relative '../lib/unit_conversion'
module Parsley module Parsley
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.2 config.load_defaults 7.2
config.autoload_lib(ignore: %w(assets tasks unit_conversion)) config.autoload_lib(ignore: %w(assets tasks unit_conversion unit_conversion.rb))
config.action_dispatch.cookies_same_site_protection = :lax config.action_dispatch.cookies_same_site_protection = :lax
config.active_record.collection_cache_versioning = true config.active_record.collection_cache_versioning = true

View File

@ -1,11 +1,11 @@
require 'unit_conversion/constants' require_relative './unit_conversion/constants'
require 'unit_conversion/errors' require_relative './unit_conversion/errors'
require 'unit_conversion/formatters' require_relative './unit_conversion/formatters'
require 'unit_conversion/parsed_number' require_relative './unit_conversion/parsed_number'
require 'unit_conversion/parsed_unit' require_relative './unit_conversion/parsed_unit'
require 'unit_conversion/conversions' require_relative './unit_conversion/conversions'
require 'unit_conversion/unitwise_patch' require_relative './unit_conversion/unitwise_patch'
require 'unit_conversion/value_unit' require_relative './unit_conversion/value_unit'
module UnitConversion module UnitConversion
class << self class << self

View File

@ -19,6 +19,9 @@ RSpec.describe Food, type: :model do
i.density = '5 mile/hour' i.density = '5 mile/hour'
expect(i).not_to be_valid expect(i).not_to be_valid
i.density = '1 g/ml'
expect(i).to be_valid
end end
end end