Continue converting to composition api
This commit is contained in:
parent
a071e6b21e
commit
0d35f50dbf
@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { computed, nextTick, useTemplateRef, watch } from "vue";
|
||||
import { computed, nextTick, onMounted, onUpdated, useTemplateRef } from "vue";
|
||||
|
||||
import Caret from "../iconic/svg/smart/caret";
|
||||
import Check from "../iconic/svg/smart/check";
|
||||
@ -130,14 +130,18 @@
|
||||
svgElement.value.update();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.icon,
|
||||
() => {
|
||||
ensureSvgApi(svgName.value, svgData.value.scriptBlocks);
|
||||
nextTick(() => setupSvgApi(svgName.value));
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
function updateScripts() {
|
||||
ensureSvgApi(svgName.value, svgData.value.scriptBlocks);
|
||||
setupSvgApi(svgName.value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateScripts();
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
updateScripts();
|
||||
});
|
||||
|
||||
return {
|
||||
svgData,
|
||||
|
@ -11,96 +11,81 @@
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pagedItemName: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
import { computed } from "vue";
|
||||
|
||||
currentPage: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
const emit = defineEmits(["changePage"]);
|
||||
|
||||
totalPages: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
const props = defineProps({
|
||||
pagedItemName: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
pageWindow: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
currentPage: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
|
||||
pageOuterWindow: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
totalPages: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
|
||||
showWithSinglePage: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
pageWindow: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
|
||||
computed: {
|
||||
pageItems() {
|
||||
const items = new Set();
|
||||
pageOuterWindow: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
|
||||
for (let x = 0; x < this.pageOuterWindow; x++) {
|
||||
items.add(x + 1);
|
||||
items.add(this.totalPages - x);
|
||||
}
|
||||
|
||||
const start = this.currentPage - Math.ceil(this.pageWindow / 2);
|
||||
const end = this.currentPage + Math.floor(this.pageWindow / 2);
|
||||
|
||||
for (let x = start; x <= end; x++) {
|
||||
items.add(x);
|
||||
}
|
||||
|
||||
let emptySpace = -1;
|
||||
const finalList = [];
|
||||
|
||||
[...items.values()].filter(p => p > 0 && p <= this.totalPages).sort((a, b) => a - b).forEach((p, idx, list) => {
|
||||
finalList.push(p);
|
||||
if (list[idx + 1] && list[idx + 1] !== p + 1) {
|
||||
finalList.push(emptySpace--);
|
||||
}
|
||||
});
|
||||
|
||||
return finalList;
|
||||
},
|
||||
|
||||
isLastPage() {
|
||||
return this.currentPage === this.totalPages;
|
||||
},
|
||||
|
||||
isFirstPage() {
|
||||
return this.currentPage === 1;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changePage(idx) {
|
||||
this.$emit("changePage", idx);
|
||||
}
|
||||
}
|
||||
showWithSinglePage: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const pageItems = computed(() => {
|
||||
const items = new Set();
|
||||
|
||||
for (let x = 0; x < props.pageOuterWindow; x++) {
|
||||
items.add(x + 1);
|
||||
items.add(props.totalPages - x);
|
||||
}
|
||||
|
||||
const start = props.currentPage - Math.ceil(props.pageWindow / 2);
|
||||
const end = props.currentPage + Math.floor(props.pageWindow / 2);
|
||||
|
||||
for (let x = start; x <= end; x++) {
|
||||
items.add(x);
|
||||
}
|
||||
|
||||
let emptySpace = -1;
|
||||
const finalList = [];
|
||||
|
||||
[...items.values()].filter(p => p > 0 && p <= props.totalPages).sort((a, b) => a - b).forEach((p, idx, list) => {
|
||||
finalList.push(p);
|
||||
if (list[idx + 1] && list[idx + 1] !== p + 1) {
|
||||
finalList.push(emptySpace--);
|
||||
}
|
||||
});
|
||||
|
||||
return finalList;
|
||||
});
|
||||
|
||||
const isLastPage = computed(() => props.currentPage === props.totalPages);
|
||||
const isFirstPage = computed(() => props.currentPage === 1);
|
||||
|
||||
function changePage(idx) {
|
||||
emit("changePage", idx);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
ul.pagination {
|
||||
|
||||
}
|
||||
|
||||
</style>
|
@ -9,97 +9,85 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { computed, ref, useTemplateRef } from "vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
starCount: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
readonly: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
const props = defineProps({
|
||||
starCount: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
|
||||
step: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 0.5
|
||||
},
|
||||
readonly: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
step: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 0.5
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
temporaryValue: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
ratingPercent() {
|
||||
return ((this.modelValue || 0) / this.starCount) * 100.0;
|
||||
},
|
||||
|
||||
temporaryPercent() {
|
||||
if (this.temporaryValue !== null) {
|
||||
return (this.temporaryValue / this.starCount) * 100.0;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
filledStyle() {
|
||||
const width = this.temporaryPercent || this.ratingPercent;
|
||||
return {
|
||||
width: width + "%"
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick(evt) {
|
||||
if (this.temporaryValue !== null) {
|
||||
this.$emit("update:modelValue", this.temporaryValue);
|
||||
}
|
||||
},
|
||||
|
||||
handleMousemove(evt) {
|
||||
if (this.readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapperBox = this.$refs.wrapper.getBoundingClientRect();
|
||||
const wrapperWidth = wrapperBox.right - wrapperBox.left;
|
||||
const mousePosition = evt.clientX;
|
||||
|
||||
if (mousePosition > wrapperBox.left && mousePosition < wrapperBox.right) {
|
||||
const filledRatio = ((mousePosition - wrapperBox.left) / wrapperWidth);
|
||||
|
||||
const totalSteps = this.starCount / this.step;
|
||||
const filledSteps = Math.round(totalSteps * filledRatio);
|
||||
|
||||
this.temporaryValue = filledSteps * this.step;
|
||||
}
|
||||
},
|
||||
|
||||
handleMouseleave(evt) {
|
||||
this.temporaryValue = null;
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
}
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
|
||||
const temporaryValue = ref(null);
|
||||
const ratingPercent = computed(() => ((props.modelValue || 0) / props.starCount) * 100.0);
|
||||
const wrapperEl = useTemplateRef("wrapper");
|
||||
|
||||
const temporaryPercent = computed(() => {
|
||||
if (temporaryValue.value !== null) {
|
||||
return (temporaryValue.value / props.starCount) * 100.0;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const filledStyle = computed(() => {
|
||||
const width = temporaryPercent.value || ratingPercent.value;
|
||||
return {
|
||||
width: width + "%"
|
||||
};
|
||||
});
|
||||
|
||||
function handleClick(evt) {
|
||||
if (temporaryValue.value !== null) {
|
||||
emit("update:modelValue", temporaryValue.value);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMousemove(evt) {
|
||||
if (props.readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapperBox = wrapperEl.value.getBoundingClientRect();
|
||||
const wrapperWidth = wrapperBox.right - wrapperBox.left;
|
||||
const mousePosition = evt.clientX;
|
||||
|
||||
if (mousePosition > wrapperBox.left && mousePosition < wrapperBox.right) {
|
||||
const filledRatio = ((mousePosition - wrapperBox.left) / wrapperWidth);
|
||||
|
||||
const totalSteps = props.starCount / props.step;
|
||||
const filledSteps = Math.round(totalSteps * filledRatio);
|
||||
|
||||
temporaryValue.value = filledSteps * props.step;
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseleave(evt) {
|
||||
temporaryValue.value = null;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -6,61 +6,45 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { ref } from "vue";
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
const text = ref(null);
|
||||
|
||||
const triggerInput = debounce(function() {
|
||||
emit("update:modelValue", text.value);
|
||||
},
|
||||
250,
|
||||
{ leading: false, trailing: true })
|
||||
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
function userUpdateText(newText) {
|
||||
if (text.value !== newText) {
|
||||
text.value = newText;
|
||||
triggerInput();
|
||||
}
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
text: null
|
||||
};
|
||||
},
|
||||
|
||||
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) {
|
||||
if (this.text === null && this.text !== text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$watch("modelValue",
|
||||
val => this.propUpdateText(val),
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
function propUpdateText(newText) {
|
||||
if (text.value === null && text.value !== newText) {
|
||||
text.value = newText;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,79 +7,66 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
required: true,
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
import {computed, nextTick, ref, useTemplateRef} from "vue";
|
||||
|
||||
data() {
|
||||
return {
|
||||
hasFocus: false
|
||||
};
|
||||
},
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
computed: {
|
||||
tagText() {
|
||||
return this.modelValue.join(" ");
|
||||
}
|
||||
},
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
required: true,
|
||||
type: Array
|
||||
}
|
||||
});
|
||||
|
||||
watch: {
|
||||
},
|
||||
const hasFocus = ref(false);
|
||||
const tagText = computed(() => props.modelValue.join(" "));
|
||||
const inputElement = useTemplateRef("input");
|
||||
|
||||
methods: {
|
||||
inputHandler(el) {
|
||||
let str = el.target.value;
|
||||
this.checkInput(str);
|
||||
this.$nextTick(() => {
|
||||
el.target.value = str;
|
||||
});
|
||||
},
|
||||
function inputHandler(el) {
|
||||
let str = el.target.value;
|
||||
checkInput(str);
|
||||
nextTick(() => {
|
||||
el.target.value = str;
|
||||
});
|
||||
}
|
||||
|
||||
checkInput(str) {
|
||||
if (this.hasFocus) {
|
||||
const m = str.match(/\S\s+\S*$/);
|
||||
function checkInput(str) {
|
||||
if (hasFocus.value) {
|
||||
const m = str.match(/\S\s+\S*$/);
|
||||
|
||||
if (m !== null) {
|
||||
str = str.substring(0, m.index + 1);
|
||||
} else {
|
||||
str = "";
|
||||
}
|
||||
}
|
||||
|
||||
const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))];
|
||||
|
||||
if (!this.arraysEqual(newTags, this.modelValue)) {
|
||||
this.$emit("update:modelValue", newTags);
|
||||
}
|
||||
},
|
||||
|
||||
getFocus() {
|
||||
this.hasFocus = true;
|
||||
},
|
||||
|
||||
loseFocus() {
|
||||
this.hasFocus = false;
|
||||
this.checkInput(this.$refs.input.value);
|
||||
|
||||
},
|
||||
|
||||
arraysEqual(arr1, arr2) {
|
||||
if(arr1.length !== arr2.length)
|
||||
return false;
|
||||
for(let i = arr1.length; i--;) {
|
||||
if(arr1[i] !== arr2[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
if (m !== null) {
|
||||
str = str.substring(0, m.index + 1);
|
||||
} else {
|
||||
str = "";
|
||||
}
|
||||
}
|
||||
|
||||
const newTags = [...new Set(str.toString().split(/\s+/).filter(t => t.length > 0))];
|
||||
|
||||
if (!arraysEqual(newTags, props.modelValue)) {
|
||||
emit("update:modelValue", newTags);
|
||||
}
|
||||
}
|
||||
|
||||
function getFocus() {
|
||||
hasFocus.value = true;
|
||||
}
|
||||
|
||||
function loseFocus() {
|
||||
hasFocus.value = false;
|
||||
checkInput(inputElement.value.value);
|
||||
}
|
||||
|
||||
function arraysEqual(arr1, arr2) {
|
||||
if(arr1.length !== arr2.length)
|
||||
return false;
|
||||
for(let i = arr1.length; i--;) {
|
||||
if(arr1[i] !== arr2[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
</script>
|
@ -2,8 +2,8 @@
|
||||
<div class="field">
|
||||
<label v-if="label.length" class="label is-small-mobile">{{ label }}</label>
|
||||
<div :class="controlClasses">
|
||||
<textarea v-if="isTextarea" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled"></textarea>
|
||||
<input v-else :type="type" :class="inputClasses" :value="modelValue" @input="input" :disabled="disabled">
|
||||
<textarea v-if="isTextarea" :class="inputClasses" v-model="model" :disabled="disabled"></textarea>
|
||||
<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>
|
||||
</div>
|
||||
<p v-if="helpMessage !== null" :class="helpClasses">
|
||||
@ -12,81 +12,67 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
validationError: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
import { computed } from "vue";
|
||||
|
||||
computed: {
|
||||
isTextarea() {
|
||||
return this.type === "textarea";
|
||||
},
|
||||
|
||||
controlClasses() {
|
||||
return [
|
||||
"control",
|
||||
{
|
||||
"has-icons-right": this.validationError !== null
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
inputClasses() {
|
||||
return [
|
||||
"is-small-mobile",
|
||||
{
|
||||
"textarea": this.isTextarea,
|
||||
"input": !this.isTextarea,
|
||||
"is-danger": this.validationError !== null
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
helpMessage() {
|
||||
return this.validationError;
|
||||
},
|
||||
|
||||
helpClasses() {
|
||||
return [
|
||||
"help",
|
||||
{
|
||||
"is-danger": this.validationError !== null
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
input(evt) {
|
||||
this.$emit("update:modelValue", evt.target.value);
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
label: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
modelValue: {
|
||||
required: false,
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
validationError: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const model = defineModel({
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
});
|
||||
|
||||
const isTextarea = computed(() => props.type === "textarea");
|
||||
const controlClasses = computed(() => [
|
||||
"control",
|
||||
{
|
||||
"has-icons-right": props.validationError !== null
|
||||
}
|
||||
]);
|
||||
|
||||
const inputClasses = computed(() =>[
|
||||
"is-small-mobile",
|
||||
{
|
||||
"textarea": isTextarea.value,
|
||||
"input": !isTextarea.value,
|
||||
"is-danger": props.validationError !== null
|
||||
}
|
||||
]);
|
||||
|
||||
const helpMessage = computed(() => props.validationError);
|
||||
const helpClasses = computed(() => [
|
||||
"help",
|
||||
{
|
||||
"is-danger": props.validationError !== null
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -6,16 +6,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
errors: {
|
||||
required: false,
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
errors: {
|
||||
required: false,
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
@ -144,100 +144,82 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { computed } from "vue";
|
||||
import api from "../lib/Api";
|
||||
import { mapState } from "pinia";
|
||||
import { useNutrientStore } from "../stores/nutrient";
|
||||
import { useLoadResource } from "../lib/useLoadResource";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
food: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
validationErrors: {
|
||||
required: false,
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
action: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: "Editing"
|
||||
}
|
||||
const nutrientStore = useNutrientStore();
|
||||
const nutrients = computed(() => nutrientStore.nutrientList);
|
||||
const { loadResource } = useLoadResource();
|
||||
|
||||
const props = defineProps({
|
||||
food: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
validationErrors: {
|
||||
required: false,
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
|
||||
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,
|
||||
name: null,
|
||||
gram_weight: null
|
||||
});
|
||||
},
|
||||
|
||||
removeUnit(unit) {
|
||||
if (unit.id) {
|
||||
unit._destroy = true;
|
||||
} else {
|
||||
const idx = this.food.food_units.findIndex(i => i === unit);
|
||||
this.food.food_units.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
|
||||
removeNdbn() {
|
||||
this.food.ndbn = null;
|
||||
this.food.usda_food_name = null;
|
||||
this.food.ndbn_units = [];
|
||||
},
|
||||
|
||||
updateSearchItems(text) {
|
||||
return api.getUsdaFoodSearch(text)
|
||||
.then(data => data.map(f => {
|
||||
return {
|
||||
name: f.name,
|
||||
ndbn: f.ndbn,
|
||||
description: ["#", f.ndbn, ", Cal:", f.kcal, ", Carbs:", f.carbohydrates, ", Fat:", f.lipid, ", Protein:", f.protein].join("")
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
searchItemSelected(food) {
|
||||
this.food.ndbn = food.ndbn;
|
||||
this.food.usda_food_name = food.name;
|
||||
this.food.ndbn_units = [];
|
||||
|
||||
this.loadResource(
|
||||
api.postIngredientSelectNdbn(this.food)
|
||||
.then(i => Object.assign(this.food, i))
|
||||
);
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: "Editing"
|
||||
}
|
||||
});
|
||||
|
||||
const visibleFoodUnits = computed(() => props.food.food_units.filter(iu => iu._destroy !== true));
|
||||
const hasNdbn = computed(() => props.food.ndbn !== null);
|
||||
|
||||
function addUnit() {
|
||||
props.food.food_units.push({
|
||||
id: null,
|
||||
name: null,
|
||||
gram_weight: null
|
||||
});
|
||||
}
|
||||
|
||||
function removeUnit(unit) {
|
||||
if (unit.id) {
|
||||
unit._destroy = true;
|
||||
} else {
|
||||
const idx = props.food.food_units.findIndex(i => i === unit);
|
||||
props.food.food_units.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function removeNdbn() {
|
||||
props.food.ndbn = null;
|
||||
props.food.usda_food_name = null;
|
||||
props.food.ndbn_units = [];
|
||||
}
|
||||
|
||||
function updateSearchItems(text) {
|
||||
return api.getUsdaFoodSearch(text)
|
||||
.then(data => data.map(f => {
|
||||
return {
|
||||
name: f.name,
|
||||
ndbn: f.ndbn,
|
||||
description: ["#", f.ndbn, ", Cal:", f.kcal, ", Carbs:", f.carbohydrates, ", Fat:", f.lipid, ", Protein:", f.protein].join("")
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function searchItemSelected(food) {
|
||||
props.food.ndbn = food.ndbn;
|
||||
props.food.usda_food_name = food.name;
|
||||
props.food.ndbn_units = [];
|
||||
|
||||
loadResource(
|
||||
api.postIngredientSelectNdbn(props.food)
|
||||
.then(i => Object.assign(props.food, i))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
@ -53,25 +53,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { mapState } from "pinia";
|
||||
import { computed } from "vue";
|
||||
import { useNutrientStore } from "../stores/nutrient";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
food: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(useNutrientStore, {
|
||||
nutrients: 'nutrientList'
|
||||
}),
|
||||
const props = defineProps({
|
||||
food: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const nutrientStore = useNutrientStore();
|
||||
const nutrients = computed(() => nutrientStore.nutrientList);
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -31,27 +31,21 @@
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import RecipeEdit from "./RecipeEdit";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
log: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
validationErrors: {
|
||||
required: false,
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
const props = defineProps({
|
||||
log: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
|
||||
components: {
|
||||
RecipeEdit
|
||||
}
|
||||
}
|
||||
validationErrors: {
|
||||
required: false,
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -44,22 +44,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import RecipeShow from "./RecipeShow";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
log: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
RecipeShow
|
||||
const props = defineProps({
|
||||
log: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -19,37 +19,27 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
note: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
import { computed } from "vue";
|
||||
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
canSave() {
|
||||
return this.note && this.note.content && this.note.content.length;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
this.$emit("save", this.note);
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.$emit("cancel");
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(["save", "cancel"]);
|
||||
const props = defineProps({
|
||||
note: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
});
|
||||
|
||||
const canSave = computed(() => props.note?.content?.length);
|
||||
|
||||
function save() {
|
||||
emit("save", props.note);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
emit("cancel");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -206,67 +206,47 @@ _underline_
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { computed, ref, watch } from "vue";
|
||||
//import autosize from "autosize";
|
||||
import debounce from "lodash/debounce";
|
||||
import api from "../lib/Api";
|
||||
|
||||
import RecipeEditIngredientEditor from "./RecipeEditIngredientEditor";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
recipe: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
forLogging: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
const props = defineProps({
|
||||
recipe: {
|
||||
required: true,
|
||||
type: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
stepPreviewCache: null,
|
||||
isDescriptionHelpOpen: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
stepPreview() {
|
||||
if (this.stepPreviewCache === null) {
|
||||
return this.recipe.rendered_steps;
|
||||
} else {
|
||||
return this.stepPreviewCache;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
updatePreview: debounce(function() {
|
||||
api.postPreviewSteps(this.recipe.step_text)
|
||||
.then(data => this.stepPreviewCache = data.rendered_steps)
|
||||
.catch(err => this.stepPreviewCache = "?? Error ??");
|
||||
}, 750)
|
||||
},
|
||||
|
||||
watch: {
|
||||
'recipe.step_text': function() {
|
||||
this.updatePreview();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
//autosize(this.$refs.step_text_area);
|
||||
},
|
||||
|
||||
components: {
|
||||
RecipeEditIngredientEditor
|
||||
forLogging: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const stepPreviewCache = ref(null);
|
||||
const isDescriptionHelpOpen = ref(false);
|
||||
|
||||
const stepPreview = computed(() => {
|
||||
if (stepPreviewCache.value === null) {
|
||||
return props.recipe.rendered_steps;
|
||||
} else {
|
||||
return stepPreviewCache.value;
|
||||
}
|
||||
});
|
||||
|
||||
const updatePreview = debounce(function() {
|
||||
api.postPreviewSteps(props.recipe.step_text)
|
||||
.then(data => stepPreviewCache.value = data.rendered_steps)
|
||||
.catch(err => stepPreviewCache.value = "?? Error ??");
|
||||
}, 750);
|
||||
|
||||
watch(
|
||||
() => props.recipe.step_text,
|
||||
() => updatePreview()
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<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>
|
||||
|
||||
@ -25,10 +25,10 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<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 colspan="5"></td>
|
||||
</tr>
|
||||
@ -49,10 +49,12 @@
|
||||
<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-dropdown hover v-if="isLoggedIn" class="is-right">
|
||||
<button slot="button" class="button is-small">
|
||||
<app-icon icon="menu"></app-icon>
|
||||
</button>
|
||||
<app-dropdown hover v-if="appConfig.isLoggedIn" class="is-right">
|
||||
<template #button>
|
||||
<button class="button is-small">
|
||||
<app-icon icon="menu"></app-icon>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<div class="dropdown-item">
|
||||
<router-link :to="{name: 'new_log', params: { recipeId: r.id } }" class="button is-primary is-fullwidth">
|
||||
@ -88,232 +90,212 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
import { useRouter } from 'vue-router'
|
||||
import api from "../lib/Api";
|
||||
import { mapWritableState, mapState } from "pinia";
|
||||
import AppLoading from "./AppLoading";
|
||||
import { useAppConfigStore } from "../stores/appConfig";
|
||||
import { useMediaQueryStore } from "../stores/mediaQuery";
|
||||
import { useLoadResource } from "../lib/useLoadResource";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
searchQuery: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {}
|
||||
}
|
||||
},
|
||||
const appConfig = useAppConfigStore();
|
||||
const mediaQueries = useMediaQueryStore();
|
||||
const { loadResource, localLoading } = useLoadResource();
|
||||
const router = useRouter();
|
||||
|
||||
data() {
|
||||
return {
|
||||
recipeData: null,
|
||||
recipeForDeletion: null
|
||||
};
|
||||
},
|
||||
const props = defineProps({
|
||||
searchQuery: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {}
|
||||
}
|
||||
});
|
||||
|
||||
computed: {
|
||||
...mapState(useMediaQueryStore, { isTouch: store => store.touch }),
|
||||
...mapWritableState(useAppConfigStore, [
|
||||
"initialLoad"
|
||||
]),
|
||||
const tableHeader = [
|
||||
{name: 'name', label: 'Name', sort: true},
|
||||
{name: 'tags', label: 'Tags', sort: false},
|
||||
{name: 'rating', label: 'Rating', sort: true},
|
||||
{name: 'yields', label: 'Yields', sort: false},
|
||||
{name: 'total_time', label: 'Time', sort: true},
|
||||
{name: 'created_at', label: 'Created', sort: true}
|
||||
];
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
const recipeData = ref(null);
|
||||
const recipeForDeletion = ref(null);
|
||||
const isTouch = computed(() => mediaQueries.touch);
|
||||
|
||||
recipes() {
|
||||
if (this.recipeData) {
|
||||
return this.recipeData.recipes;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
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
|
||||
}));
|
||||
|
||||
tableHeader() {
|
||||
return [
|
||||
{name: 'name', label: 'Name', sort: true},
|
||||
{name: 'tags', label: 'Tags', sort: false},
|
||||
{name: 'rating', label: 'Rating', sort: true},
|
||||
{name: 'yields', label: 'Yields', sort: false},
|
||||
{name: 'total_time', label: 'Time', sort: true},
|
||||
{name: 'created_at', label: 'Created', sort: true}
|
||||
]
|
||||
},
|
||||
const recipes = computed(() => {
|
||||
if (recipeData.value) {
|
||||
return recipeData.value.recipes;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
totalPages() {
|
||||
if (this.recipeData) {
|
||||
return this.recipeData.total_pages;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
const totalPages = computed(() => {
|
||||
if (recipeData.value) {
|
||||
return recipeData.value.total_pages;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
currentPage() {
|
||||
if (this.recipeData) {
|
||||
return this.recipeData.current_page;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
const currentPage = computed(() => {
|
||||
if (recipeData.value) {
|
||||
return recipeData.value.current_page;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
showConfirmRecipeDelete() {
|
||||
return this.recipeForDeletion !== null;
|
||||
},
|
||||
const showConfirmRecipeDelete = computed(() => recipeForDeletion.value !== null);
|
||||
|
||||
confirmRecipeDeleteMessage() {
|
||||
if (this.showConfirmRecipeDelete) {
|
||||
return `Are you sure you want to delete ${this.recipeForDeletion.name}?`;
|
||||
} else {
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
},
|
||||
const confirmRecipeDeleteMessage = computed(() => {
|
||||
if (showConfirmRecipeDelete.value) {
|
||||
return `Are you sure you want to delete ${recipeForDeletion.value.name}?`;
|
||||
} else {
|
||||
return "??";
|
||||
}
|
||||
});
|
||||
|
||||
methods: {
|
||||
buildQueryParams() {
|
||||
return {
|
||||
name: this.searchQuery.name,
|
||||
tags: this.searchQuery.tags,
|
||||
column: this.searchQuery.column,
|
||||
direction: this.searchQuery.direction,
|
||||
page: this.searchQuery.page,
|
||||
per: this.searchQuery.per
|
||||
}
|
||||
},
|
||||
watch(search, () => {
|
||||
getList().then(() => appConfig.initialLoad = true);
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
});
|
||||
|
||||
redirectToParams(params) {
|
||||
const rParams = {};
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
if (params.name) {
|
||||
rParams.name = params.name;
|
||||
}
|
||||
|
||||
if (params.tags) {
|
||||
rParams.tags = params.tags;
|
||||
}
|
||||
|
||||
if (params.column) {
|
||||
rParams.column = params.column;
|
||||
}
|
||||
|
||||
if (params.direction) {
|
||||
rParams.direction = params.direction;
|
||||
}
|
||||
|
||||
if (params.page) {
|
||||
rParams.page = params.page;
|
||||
}
|
||||
|
||||
if (params.per) {
|
||||
rParams.per = params.per;
|
||||
}
|
||||
|
||||
this.$router.push({name: 'recipeList', query: rParams});
|
||||
},
|
||||
|
||||
changePage(idx) {
|
||||
const p = this.buildQueryParams();
|
||||
p.page = idx;
|
||||
this.redirectToParams(p);
|
||||
},
|
||||
|
||||
setSort(col) {
|
||||
const p = this.buildQueryParams();
|
||||
|
||||
if (p.column === col) {
|
||||
p.direction = p.direction === "desc" ? "asc" : "desc";
|
||||
} else {
|
||||
p.column = col;
|
||||
p.direction = "asc";
|
||||
}
|
||||
this.redirectToParams(p);
|
||||
},
|
||||
|
||||
setSearchName(name) {
|
||||
const p = this.buildQueryParams();
|
||||
if (name !== p.name) {
|
||||
p.name = name;
|
||||
p.page = null;
|
||||
this.redirectToParams(p);
|
||||
}
|
||||
},
|
||||
|
||||
setSearchTags(tags) {
|
||||
const p = this.buildQueryParams();
|
||||
if (tags !== p.tags) {
|
||||
p.tags = tags;
|
||||
p.page = null;
|
||||
this.redirectToParams(p);
|
||||
}
|
||||
},
|
||||
|
||||
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() {
|
||||
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 = "";
|
||||
|
||||
if (total && total > 0) {
|
||||
str += total;
|
||||
}
|
||||
|
||||
if (active && active > 0) {
|
||||
if (str.length) {
|
||||
str += " (" + active + ")";
|
||||
} else {
|
||||
str += active;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$watch("search",
|
||||
() => {
|
||||
this.getList().then(() => this.initialLoad = true);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
components: {
|
||||
AppLoading
|
||||
function buildQueryParams() {
|
||||
return {
|
||||
name: props.searchQuery.name,
|
||||
tags: props.searchQuery.tags,
|
||||
column: props.searchQuery.column,
|
||||
direction: props.searchQuery.direction,
|
||||
page: props.searchQuery.page,
|
||||
per: props.searchQuery.per
|
||||
}
|
||||
}
|
||||
|
||||
function redirectToParams(params) {
|
||||
const rParams = {};
|
||||
|
||||
if (params.name) {
|
||||
rParams.name = params.name;
|
||||
}
|
||||
|
||||
if (params.tags) {
|
||||
rParams.tags = params.tags;
|
||||
}
|
||||
|
||||
if (params.column) {
|
||||
rParams.column = params.column;
|
||||
}
|
||||
|
||||
if (params.direction) {
|
||||
rParams.direction = params.direction;
|
||||
}
|
||||
|
||||
if (params.page) {
|
||||
rParams.page = params.page;
|
||||
}
|
||||
|
||||
if (params.per) {
|
||||
rParams.per = params.per;
|
||||
}
|
||||
|
||||
router.push({name: 'recipeList', query: rParams});
|
||||
}
|
||||
|
||||
function changePage(idx) {
|
||||
const p = buildQueryParams();
|
||||
p.page = idx;
|
||||
redirectToParams(p);
|
||||
}
|
||||
|
||||
function setSort(col) {
|
||||
const p = buildQueryParams();
|
||||
|
||||
if (p.column === col) {
|
||||
p.direction = p.direction === "desc" ? "asc" : "desc";
|
||||
} else {
|
||||
p.column = col;
|
||||
p.direction = "asc";
|
||||
}
|
||||
redirectToParams(p);
|
||||
}
|
||||
|
||||
function setSearchName(name) {
|
||||
const p = buildQueryParams();
|
||||
if (name !== p.name) {
|
||||
p.name = name;
|
||||
p.page = null;
|
||||
redirectToParams(p);
|
||||
}
|
||||
}
|
||||
|
||||
function setSearchTags(tags) {
|
||||
const p = buildQueryParams();
|
||||
if (tags !== p.tags) {
|
||||
p.tags = tags;
|
||||
p.page = null;
|
||||
redirectToParams(p);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteRecipe(recipe) {
|
||||
recipeForDeletion.value = recipe;
|
||||
}
|
||||
|
||||
function recipeDeleteConfirm() {
|
||||
if (recipeForDeletion.value !== null) {
|
||||
loadResource(
|
||||
api.deleteRecipe(recipeForDeletion.value.id).then(() => {
|
||||
recipeForDeletion.value = null;
|
||||
return getList();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function recipeDeleteCancel() {
|
||||
recipeForDeletion.value = null;
|
||||
}
|
||||
|
||||
function formatRecipeTime(total, active) {
|
||||
let str = "";
|
||||
|
||||
if (total && total > 0) {
|
||||
str += total;
|
||||
}
|
||||
|
||||
if (active && active > 0) {
|
||||
if (str.length) {
|
||||
str += " (" + active + ")";
|
||||
} else {
|
||||
str += active;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -19,12 +19,14 @@ require "rails/test_unit/railtie"
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
require_relative '../lib/unit_conversion'
|
||||
|
||||
module Parsley
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
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.active_record.collection_cache_versioning = true
|
||||
|
@ -1,11 +1,11 @@
|
||||
require 'unit_conversion/constants'
|
||||
require 'unit_conversion/errors'
|
||||
require 'unit_conversion/formatters'
|
||||
require 'unit_conversion/parsed_number'
|
||||
require 'unit_conversion/parsed_unit'
|
||||
require 'unit_conversion/conversions'
|
||||
require 'unit_conversion/unitwise_patch'
|
||||
require 'unit_conversion/value_unit'
|
||||
require_relative './unit_conversion/constants'
|
||||
require_relative './unit_conversion/errors'
|
||||
require_relative './unit_conversion/formatters'
|
||||
require_relative './unit_conversion/parsed_number'
|
||||
require_relative './unit_conversion/parsed_unit'
|
||||
require_relative './unit_conversion/conversions'
|
||||
require_relative './unit_conversion/unitwise_patch'
|
||||
require_relative './unit_conversion/value_unit'
|
||||
|
||||
module UnitConversion
|
||||
class << self
|
||||
|
@ -19,6 +19,9 @@ RSpec.describe Food, type: :model do
|
||||
|
||||
i.density = '5 mile/hour'
|
||||
expect(i).not_to be_valid
|
||||
|
||||
i.density = '1 g/ml'
|
||||
expect(i).to be_valid
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user