mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-02 20:15:24 +02:00
Use composition API for more components, enable more type checking (#914)
* Activate more linting rules from eslint and typescript * Properly add VForm as type information * Fix usage of native types * Fix more linting issues * Rename vuetify types file, add VTooltip * Fix some more typing problems * Use composition API for more components * Convert RecipeRating * Convert RecipeNutrition * Convert more components to composition API * Fix globals plugin for type checking * Add missing icon types * Fix vuetify types in Nuxt context * Use composition API for RecipeActionMenu * Convert error.vue to composition API * Convert RecipeContextMenu to composition API * Use more composition API and type checking in recipe/create * Convert AppButtonUpload to composition API * Fix some type checking in RecipeContextMenu * Remove unused components BaseAutoForm and BaseColorPicker * Convert RecipeCategoryTagDialog to composition API * Convert RecipeCardSection to composition API * Convert RecipeCategoryTagSelector to composition API * Properly import vuetify type definitions * Convert BaseButton to composition API * Convert AutoForm to composition API * Remove unused requests API file * Remove static routes from recipe API * Fix more type errors * Convert AppHeader to composition API, fixing some search bar focus problems * Convert RecipeDialogSearch to composition API * Update API types from pydantic models, handle undefined values * Improve more typing problems * Add types to other plugins * Properly type the CRUD API access * Fix typing of static image routes * Fix more typing stuff * Fix some more typing problems * Turn off more rules
This commit is contained in:
parent
d5ab5ec66f
commit
86c99b10a2
114 changed files with 2218 additions and 2033 deletions
|
@ -22,50 +22,58 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, useContext } from "@nuxtjs/composition-api";
|
||||
|
||||
const UPDATE_EVENT = "input";
|
||||
export default {
|
||||
export default defineComponent({
|
||||
props: {
|
||||
importBackup: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
recipes: {
|
||||
value: true,
|
||||
text: this.$t("general.recipes"),
|
||||
},
|
||||
users: {
|
||||
value: true,
|
||||
text: this.$t("user.users"),
|
||||
},
|
||||
groups: {
|
||||
value: true,
|
||||
text: this.$t("group.groups"),
|
||||
},
|
||||
setup(_, context) {
|
||||
const { i18n } = useContext();
|
||||
|
||||
const options = {
|
||||
recipes: {
|
||||
value: true,
|
||||
text: i18n.t("general.recipes"),
|
||||
},
|
||||
forceImport: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.emitValue();
|
||||
},
|
||||
methods: {
|
||||
emitValue() {
|
||||
this.$emit(UPDATE_EVENT, {
|
||||
recipes: this.options.recipes.value,
|
||||
users: {
|
||||
value: true,
|
||||
text: i18n.t("user.users"),
|
||||
},
|
||||
groups: {
|
||||
value: true,
|
||||
text: i18n.t("group.groups"),
|
||||
},
|
||||
}
|
||||
const forceImport = false;
|
||||
|
||||
function emitValue() {
|
||||
context.emit(UPDATE_EVENT, {
|
||||
recipes: options.recipes.value,
|
||||
settings: false,
|
||||
themes: false,
|
||||
pages: false,
|
||||
users: this.options.users.value,
|
||||
groups: this.options.groups.value,
|
||||
users: options.users.value,
|
||||
groups: options.groups.value,
|
||||
notifications: false,
|
||||
forceImport: this.forceImport,
|
||||
forceImport,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
emitValue();
|
||||
});
|
||||
|
||||
return {
|
||||
options,
|
||||
forceImport,
|
||||
emitValue,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -92,7 +92,8 @@
|
|||
</v-toolbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import {defineComponent, ref, useContext} from "@nuxtjs/composition-api";
|
||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||
|
||||
|
@ -101,7 +102,7 @@ const DELETE_EVENT = "delete";
|
|||
const CLOSE_EVENT = "close";
|
||||
const JSON_EVENT = "json";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: { RecipeContextMenu, RecipeFavoriteBadge },
|
||||
props: {
|
||||
slug: {
|
||||
|
@ -129,69 +130,70 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteDialog: false,
|
||||
edit: false,
|
||||
};
|
||||
},
|
||||
setup(_, context) {
|
||||
const deleteDialog = ref(false);
|
||||
|
||||
computed: {
|
||||
editorButtons() {
|
||||
return [
|
||||
{
|
||||
text: this.$t("general.delete"),
|
||||
icon: this.$globals.icons.delete,
|
||||
event: DELETE_EVENT,
|
||||
color: "error",
|
||||
},
|
||||
{
|
||||
text: this.$t("general.json"),
|
||||
icon: this.$globals.icons.codeBraces,
|
||||
event: JSON_EVENT,
|
||||
color: "accent",
|
||||
},
|
||||
{
|
||||
text: this.$t("general.close"),
|
||||
icon: this.$globals.icons.close,
|
||||
event: CLOSE_EVENT,
|
||||
color: "",
|
||||
},
|
||||
{
|
||||
text: this.$t("general.save"),
|
||||
icon: this.$globals.icons.save,
|
||||
event: SAVE_EVENT,
|
||||
color: "success",
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitHandler(event) {
|
||||
const { i18n, $globals } = useContext();
|
||||
const editorButtons = [
|
||||
{
|
||||
text: i18n.t("general.delete"),
|
||||
icon: $globals.icons.delete,
|
||||
event: DELETE_EVENT,
|
||||
color: "error",
|
||||
},
|
||||
{
|
||||
text: i18n.t("general.json"),
|
||||
icon: $globals.icons.codeBraces,
|
||||
event: JSON_EVENT,
|
||||
color: "accent",
|
||||
},
|
||||
{
|
||||
text: i18n.t("general.close"),
|
||||
icon: $globals.icons.close,
|
||||
event: CLOSE_EVENT,
|
||||
color: "",
|
||||
},
|
||||
{
|
||||
text: i18n.t("general.save"),
|
||||
icon: $globals.icons.save,
|
||||
event: SAVE_EVENT,
|
||||
color: "success",
|
||||
},
|
||||
];
|
||||
|
||||
function emitHandler(event: string) {
|
||||
switch (event) {
|
||||
case CLOSE_EVENT:
|
||||
this.$emit(CLOSE_EVENT);
|
||||
this.$emit("input", false);
|
||||
context.emit(CLOSE_EVENT);
|
||||
context.emit("input", false);
|
||||
break;
|
||||
case SAVE_EVENT:
|
||||
this.$emit(SAVE_EVENT);
|
||||
context.emit(SAVE_EVENT);
|
||||
break;
|
||||
case JSON_EVENT:
|
||||
this.$emit(JSON_EVENT);
|
||||
context.emit(JSON_EVENT);
|
||||
break;
|
||||
case DELETE_EVENT:
|
||||
this.deleteDialog = true;
|
||||
deleteDialog.value = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
emitDelete() {
|
||||
this.$emit(DELETE_EVENT);
|
||||
this.$emit("input", false);
|
||||
},
|
||||
}
|
||||
|
||||
function emitDelete() {
|
||||
context.emit(DELETE_EVENT);
|
||||
context.emit("input", false);
|
||||
}
|
||||
|
||||
return {
|
||||
deleteDialog,
|
||||
editorButtons,
|
||||
emitHandler,
|
||||
emitDelete,
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
|
||||
const BASE_URL = window.location.origin;
|
||||
|
@ -107,7 +107,6 @@ export default defineComponent({
|
|||
},
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const { $globals, i18n } = useContext();
|
||||
|
||||
const iconOptions = [
|
||||
|
@ -142,15 +141,16 @@ export default defineComponent({
|
|||
return iconOptions.find((item) => item.name === icon) || iconOptions[0];
|
||||
}
|
||||
|
||||
const { recipeAssetPath } = useStaticRoutes();
|
||||
function assetURL(assetName: string) {
|
||||
return api.recipes.recipeAssetPath(props.slug, assetName);
|
||||
return recipeAssetPath(props.slug, assetName);
|
||||
}
|
||||
|
||||
function assetEmbed(name: string) {
|
||||
return `<img src="${BASE_URL}${assetURL(name)}" height="100%" width="100%"> </img>`;
|
||||
}
|
||||
|
||||
function setFileObject(fileObject: any) {
|
||||
function setFileObject(fileObject: File) {
|
||||
state.fileObject = fileObject;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,13 +51,15 @@
|
|||
</v-lazy>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
||||
import RecipeChips from "./RecipeChips";
|
||||
import RecipeContextMenu from "./RecipeContextMenu";
|
||||
import RecipeCardImage from "./RecipeCardImage";
|
||||
import RecipeRating from "./RecipeRating";
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||
import RecipeChips from "./RecipeChips.vue";
|
||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||
import RecipeRating from "./RecipeRating.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: { RecipeFavoriteBadge, RecipeChips, RecipeContextMenu, RecipeRating, RecipeCardImage },
|
||||
props: {
|
||||
name: {
|
||||
|
@ -99,17 +101,17 @@ export default {
|
|||
default: 200,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
setup() {
|
||||
const { $auth } = useContext();
|
||||
const loggedIn = computed(() => {
|
||||
return $auth.loggedIn;
|
||||
});
|
||||
|
||||
return {
|
||||
fallBackImage: false,
|
||||
loggedIn,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return this.$store.getters.getIsLoggedIn;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, ref, watch} from "@nuxtjs/composition-api";
|
||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||
export default {
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
tiny: {
|
||||
type: Boolean,
|
||||
|
@ -50,44 +52,42 @@ export default {
|
|||
default: 200,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
setup(props) {
|
||||
const api = useUserApi();
|
||||
|
||||
const { recipeImage, recipeSmallImage, recipeTinyImage } = useStaticRoutes();
|
||||
|
||||
return { api, recipeImage, recipeSmallImage, recipeTinyImage };
|
||||
},
|
||||
data() {
|
||||
const fallBackImage = ref(false);
|
||||
const imageSize = computed(() => {
|
||||
if (props.tiny) return "tiny";
|
||||
if (props.small) return "small";
|
||||
if (props.large) return "large";
|
||||
return "large";
|
||||
})
|
||||
|
||||
watch(() => props.slug, () => {
|
||||
fallBackImage.value = false;
|
||||
});
|
||||
|
||||
function getImage(slug: string) {
|
||||
switch (imageSize.value) {
|
||||
case "tiny":
|
||||
return recipeTinyImage(slug, props.imageVersion);
|
||||
case "small":
|
||||
return recipeSmallImage(slug, props.imageVersion);
|
||||
case "large":
|
||||
return recipeImage(slug, props.imageVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fallBackImage: false,
|
||||
api,
|
||||
fallBackImage,
|
||||
imageSize,
|
||||
getImage,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
imageSize() {
|
||||
if (this.tiny) return "tiny";
|
||||
if (this.small) return "small";
|
||||
if (this.large) return "large";
|
||||
return "large";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
slug() {
|
||||
this.fallBackImage = false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getImage(slug) {
|
||||
switch (this.imageSize) {
|
||||
case "tiny":
|
||||
return this.recipeTinyImage(slug, this.imageVersion);
|
||||
case "small":
|
||||
return this.recipeSmallImage(slug, this.imageVersion);
|
||||
case "large":
|
||||
return this.recipeImage(slug, this.imageVersion);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -108,4 +108,4 @@ export default {
|
|||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -10,15 +10,7 @@
|
|||
<v-list-item three-line>
|
||||
<slot name="avatar">
|
||||
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
||||
<v-img
|
||||
v-if="!fallBackImage"
|
||||
:src="getImage(slug)"
|
||||
@load="fallBackImage = false"
|
||||
@error="fallBackImage = true"
|
||||
></v-img>
|
||||
<v-icon v-else color="primary" class="icon-position" size="100">
|
||||
{{ $globals.icons.primary }}
|
||||
</v-icon>
|
||||
<RecipeCardImage :icon-size="100" :height="125" :slug="slug" small :image-version="image"></RecipeCardImage>
|
||||
</v-list-item-avatar>
|
||||
</slot>
|
||||
<v-list-item-content>
|
||||
|
@ -61,15 +53,17 @@
|
|||
</v-expand-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
||||
import RecipeContextMenu from "./RecipeContextMenu";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
RecipeFavoriteBadge,
|
||||
RecipeContextMenu,
|
||||
RecipeCardImage,
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
|
@ -89,8 +83,9 @@ export default defineComponent({
|
|||
default: 0,
|
||||
},
|
||||
image: {
|
||||
type: [String, null],
|
||||
default: "",
|
||||
type: String,
|
||||
required: false,
|
||||
default: "abc123",
|
||||
},
|
||||
route: {
|
||||
type: Boolean,
|
||||
|
@ -102,24 +97,14 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
const { $auth } = useContext();
|
||||
const loggedIn = computed(() => {
|
||||
return $auth.loggedIn;
|
||||
});
|
||||
|
||||
return { api };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fallBackImage: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return this.$store.getters.getIsLoggedIn;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getImage(slug) {
|
||||
return this.api.recipes.recipeSmallImage(slug, this.image);
|
||||
},
|
||||
loggedIn,
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -102,13 +102,16 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RecipeCard from "./RecipeCard";
|
||||
import RecipeCardMobile from "./RecipeCardMobile";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
|
||||
import RecipeCard from "./RecipeCard.vue";
|
||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||
import { useSorter } from "~/composables/recipes";
|
||||
import {Recipe} from "~/types/api-types/recipe";
|
||||
|
||||
const SORT_EVENT = "sort";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: {
|
||||
RecipeCard,
|
||||
RecipeCardMobile,
|
||||
|
@ -126,100 +129,90 @@ export default {
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
hardLimit: {
|
||||
type: [String, Number],
|
||||
default: 99999,
|
||||
},
|
||||
mobileCards: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
singleColumn: {
|
||||
type: Boolean,
|
||||
defualt: false,
|
||||
default: false,
|
||||
},
|
||||
recipes: {
|
||||
type: Array,
|
||||
type: Array as () => Recipe[],
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
setup(props, context) {
|
||||
const utils = useSorter();
|
||||
|
||||
return { utils };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sortLoading: false,
|
||||
loading: false,
|
||||
EVENTS: {
|
||||
az: "az",
|
||||
rating: "rating",
|
||||
created: "created",
|
||||
updated: "updated",
|
||||
shuffle: "shuffle",
|
||||
},
|
||||
const EVENTS = {
|
||||
az: "az",
|
||||
rating: "rating",
|
||||
created: "created",
|
||||
updated: "updated",
|
||||
shuffle: "shuffle",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
viewScale() {
|
||||
if (this.mobileCards) return true;
|
||||
switch (this.$vuetify.breakpoint.name) {
|
||||
case "xs":
|
||||
return true;
|
||||
case "sm":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
effectiveHardLimit() {
|
||||
return Math.min(this.hardLimit, this.recipes.length);
|
||||
},
|
||||
displayTitleIcon() {
|
||||
return this.icon || this.$globals.icons.tags;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async setLoader() {
|
||||
this.loading = true;
|
||||
// eslint-disable-next-line promise/param-names
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
this.loading = false;
|
||||
},
|
||||
navigateRandom() {
|
||||
const recipe = this.recipes[Math.floor(Math.random() * this.recipes.length)];
|
||||
this.$router.push(`/recipe/${recipe.slug}`);
|
||||
},
|
||||
sortRecipes(sortType) {
|
||||
this.sortLoading = true;
|
||||
const sortTarget = [...this.recipes];
|
||||
const { $globals, $vuetify } = useContext();
|
||||
const viewScale = computed(() => {
|
||||
return props.mobileCards || $vuetify.breakpoint.smAndDown;
|
||||
});
|
||||
|
||||
const displayTitleIcon = computed(() => {
|
||||
return props.icon || $globals.icons.tags;
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
sortLoading: false,
|
||||
})
|
||||
|
||||
const router = useRouter();
|
||||
function navigateRandom() {
|
||||
if (props.recipes.length > 0) {
|
||||
const recipe = props.recipes[Math.floor(Math.random() * props.recipes.length)];
|
||||
if (recipe.slug !== undefined) {
|
||||
router.push(`/recipe/${recipe.slug}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortRecipes(sortType: string) {
|
||||
state.sortLoading = true;
|
||||
const sortTarget = [...props.recipes];
|
||||
switch (sortType) {
|
||||
case this.EVENTS.az:
|
||||
this.utils.sortAToZ(sortTarget);
|
||||
case EVENTS.az:
|
||||
utils.sortAToZ(sortTarget);
|
||||
break;
|
||||
case this.EVENTS.rating:
|
||||
this.utils.sortByRating(sortTarget);
|
||||
case EVENTS.rating:
|
||||
utils.sortByRating(sortTarget);
|
||||
break;
|
||||
case this.EVENTS.created:
|
||||
this.utils.sortByCreated(sortTarget);
|
||||
case EVENTS.created:
|
||||
utils.sortByCreated(sortTarget);
|
||||
break;
|
||||
case this.EVENTS.updated:
|
||||
this.utils.sortByUpdated(sortTarget);
|
||||
case EVENTS.updated:
|
||||
utils.sortByUpdated(sortTarget);
|
||||
break;
|
||||
case this.EVENTS.shuffle:
|
||||
this.utils.shuffle(sortTarget);
|
||||
case EVENTS.shuffle:
|
||||
utils.shuffle(sortTarget);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown Event", sortType);
|
||||
return;
|
||||
}
|
||||
this.$emit(SORT_EVENT, sortTarget);
|
||||
this.sortLoading = false;
|
||||
},
|
||||
context.emit(SORT_EVENT, sortTarget);
|
||||
state.sortLoading = false;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
EVENTS,
|
||||
viewScale,
|
||||
displayTitleIcon,
|
||||
navigateRandom,
|
||||
sortRecipes,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -40,16 +40,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, watch } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
|
||||
const CREATED_ITEM_EVENT = "created-item";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: "Add",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
|
@ -63,55 +61,49 @@ export default defineComponent({
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
setup(props, context) {
|
||||
const title = computed(() => props.tagDialog ? "Create a Tag" : "Create a Category");
|
||||
const inputLabel = computed(() => props.tagDialog ? "Tag Name" : "Category Name");
|
||||
|
||||
return { api };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
const rules = {
|
||||
required: (val: string) => !!val || "A Name is Required",
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
dialog: false,
|
||||
itemName: "",
|
||||
rules: {
|
||||
required: (val) => !!val || "A Name is Required",
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return this.tagDialog ? "Create a Tag" : "Create a Category";
|
||||
},
|
||||
inputLabel() {
|
||||
return this.tagDialog ? "Tag Name" : "Category Name";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
dialog(val) {
|
||||
if (!val) this.itemName = "";
|
||||
},
|
||||
},
|
||||
watch(() => state.dialog, (val: boolean) => {
|
||||
if (!val) state.itemName = "";
|
||||
});
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
async select() {
|
||||
const api = useUserApi();
|
||||
async function select() {
|
||||
const newItem = await (async () => {
|
||||
if (this.tagDialog) {
|
||||
const { data } = await this.api.tags.createOne({ name: this.itemName });
|
||||
if (props.tagDialog) {
|
||||
const { data } = await api.tags.createOne({ name: state.itemName });
|
||||
return data;
|
||||
} else {
|
||||
const { data } = await this.api.categories.createOne({ name: this.itemName });
|
||||
const { data } = await api.categories.createOne({ name: state.itemName });
|
||||
return data;
|
||||
}
|
||||
})();
|
||||
|
||||
console.log(newItem);
|
||||
|
||||
this.$emit(CREATED_ITEM_EVENT, newItem);
|
||||
this.dialog = false;
|
||||
},
|
||||
context.emit(CREATED_ITEM_EVENT, newItem);
|
||||
state.dialog = false;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
title,
|
||||
inputLabel,
|
||||
rules,
|
||||
select,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -42,18 +42,22 @@
|
|||
</v-autocomplete>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, reactive, toRefs, useContext, watch } from "@nuxtjs/composition-api";
|
||||
import RecipeCategoryTagDialog from "./RecipeCategoryTagDialog.vue";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useTags, useCategories } from "~/composables/recipes";
|
||||
import { Category } from "~/api/class-interfaces/categories";
|
||||
import { Tag } from "~/api/class-interfaces/tags";
|
||||
|
||||
const MOUNTED_EVENT = "mounted";
|
||||
export default {
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
RecipeCategoryTagDialog,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
type: Array as () => (Category | Tag | string)[],
|
||||
required: true,
|
||||
},
|
||||
solo: {
|
||||
|
@ -90,74 +94,74 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
|
||||
setup(props, context) {
|
||||
const { allTags, useAsyncGetAll: getAllTags } = useTags();
|
||||
const { allCategories, useAsyncGetAll: getAllCategories } = useCategories();
|
||||
getAllCategories();
|
||||
getAllTags();
|
||||
|
||||
return { api, allTags, allCategories, getAllCategories, getAllTags };
|
||||
},
|
||||
const state = reactive({
|
||||
selected: props.value,
|
||||
});
|
||||
watch(() => props.value, (val) => {
|
||||
state.selected = val;
|
||||
});
|
||||
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
};
|
||||
},
|
||||
const { i18n } = useContext();
|
||||
const inputLabel = computed(() => {
|
||||
if (!props.showLabel) return null;
|
||||
return props.tagSelector ? i18n.t("tag.tags") : i18n.t("recipe.categories");
|
||||
});
|
||||
|
||||
computed: {
|
||||
inputLabel() {
|
||||
if (!this.showLabel) return null;
|
||||
return this.tagSelector ? this.$t("tag.tags") : this.$t("recipe.categories");
|
||||
},
|
||||
activeItems() {
|
||||
let ItemObjects = [];
|
||||
if (this.tagSelector) ItemObjects = this.allTags;
|
||||
const activeItems = computed(() => {
|
||||
let itemObjects: Tag[] | Category[] | null;
|
||||
if (props.tagSelector) itemObjects = allTags.value;
|
||||
else {
|
||||
ItemObjects = this.allCategories;
|
||||
itemObjects = allCategories.value;
|
||||
}
|
||||
if (this.returnObject) return ItemObjects;
|
||||
if (props.returnObject) return itemObjects;
|
||||
else {
|
||||
return ItemObjects.map((x) => x.name);
|
||||
return itemObjects?.map((x: Tag | Category) => x.name);
|
||||
}
|
||||
},
|
||||
flat() {
|
||||
if (this.selected) {
|
||||
return this.selected.length > 0 && this.solo;
|
||||
});
|
||||
|
||||
const flat = computed(() => {
|
||||
if (state.selected) {
|
||||
return state.selected.length > 0 && props.solo;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.selected = val;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit(MOUNTED_EVENT);
|
||||
this.setInit(this.value);
|
||||
},
|
||||
methods: {
|
||||
emitChange() {
|
||||
this.$emit("input", this.selected);
|
||||
},
|
||||
setInit(val) {
|
||||
this.selected = val;
|
||||
},
|
||||
removeByIndex(index) {
|
||||
this.selected.splice(index, 1);
|
||||
},
|
||||
pushToItem(createdItem) {
|
||||
createdItem = this.returnObject ? createdItem : createdItem.name;
|
||||
function emitChange() {
|
||||
context.emit("input", state.selected);
|
||||
}
|
||||
|
||||
// TODO Is this needed?
|
||||
onMounted(() => {
|
||||
context.emit(MOUNTED_EVENT);
|
||||
});
|
||||
|
||||
function removeByIndex(index: number) {
|
||||
state.selected.splice(index, 1);
|
||||
}
|
||||
|
||||
function pushToItem(createdItem: Tag | Category) {
|
||||
// TODO: Remove excessive get calls
|
||||
this.getAllCategories();
|
||||
this.getAllTags();
|
||||
this.selected.push(createdItem);
|
||||
},
|
||||
getAllCategories();
|
||||
getAllTags();
|
||||
state.selected.push(createdItem);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
inputLabel,
|
||||
activeItems,
|
||||
flat,
|
||||
emitChange,
|
||||
removeByIndex,
|
||||
pushToItem,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, useContext, computed } from "@nuxtjs/composition-api";
|
||||
import { defineComponent, reactive, toRefs, useContext, computed, useMeta } from "@nuxtjs/composition-api";
|
||||
|
||||
type ItemType = "tags" | "categories" | "tools";
|
||||
|
||||
|
@ -54,7 +54,6 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup(props) {
|
||||
// @ts-ignore
|
||||
const { i18n, $globals } = useContext();
|
||||
|
||||
const state = reactive({
|
||||
|
@ -77,8 +76,12 @@ export default defineComponent({
|
|||
break;
|
||||
}
|
||||
|
||||
useMeta(() => ({
|
||||
title: state.headline,
|
||||
}));
|
||||
|
||||
const itemsSorted = computed(() => {
|
||||
const byLetter: { [key: string]: Array<any> } = {};
|
||||
const byLetter: { [key: string]: Array<GenericItem> } = {};
|
||||
|
||||
if (!props.items) return byLetter;
|
||||
|
||||
|
@ -99,10 +102,7 @@ export default defineComponent({
|
|||
itemsSorted,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.headline as string,
|
||||
}
|
||||
},
|
||||
// Needed for useMeta
|
||||
head: {},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent} from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
truncate: {
|
||||
type: Boolean,
|
||||
|
@ -48,39 +50,23 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
allCategories() {
|
||||
return this.$store.getters.getAllCategories || [];
|
||||
},
|
||||
allTags() {
|
||||
return this.$store.getters.getAllTags || [];
|
||||
},
|
||||
urlParam() {
|
||||
return this.isCategory ? "categories" : "tags";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getSlug(name) {
|
||||
if (!name) return;
|
||||
setup(props) {
|
||||
const urlParam = computed(() => props.isCategory ? "categories" : "tags");
|
||||
|
||||
if (this.isCategory) {
|
||||
const matches = this.allCategories.filter((x) => x.name === name);
|
||||
if (matches.length > 0) return matches[0].slug;
|
||||
} else {
|
||||
const matches = this.allTags.filter((x) => x.name === name);
|
||||
if (matches.length > 0) return matches[0].slug;
|
||||
}
|
||||
},
|
||||
truncateText(text, length = 20, clamp) {
|
||||
if (!this.truncate) return text;
|
||||
clamp = clamp || "...";
|
||||
function truncateText(text: string, length = 20, clamp = "...") {
|
||||
if (!props.truncate) return text;
|
||||
const node = document.createElement("div");
|
||||
node.innerHTML = text;
|
||||
const content = node.textContent;
|
||||
const content = node.textContent || "";
|
||||
return content.length > length ? content.slice(0, length) + clamp : content;
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
urlParam,
|
||||
truncateText,
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -167,7 +167,6 @@ export default defineComponent({
|
|||
pickerMenu: false,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const { i18n, $globals } = useContext();
|
||||
|
||||
// ===========================================================================
|
||||
|
@ -262,14 +261,12 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// Note: Print is handled as an event in the parent component
|
||||
const eventHandlers: { [key: string]: Function } = {
|
||||
// @ts-ignore - Doens't know about open()
|
||||
const eventHandlers: { [key: string]: () => void } = {
|
||||
delete: () => {
|
||||
state.recipeDeleteDialog = true;
|
||||
},
|
||||
edit: () => router.push(`/recipe/${props.slug}` + "?edit=true"),
|
||||
download: handleDownloadEvent,
|
||||
// @ts-ignore - Doens't know about open()
|
||||
mealplanner: () => {
|
||||
state.mealplannerDialog = true;
|
||||
},
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
</template>
|
||||
</v-data-table>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
||||
import RecipeChip from "./RecipeChips.vue";
|
||||
|
@ -49,13 +49,13 @@ import { UserOut } from "~/types/api-types/user";
|
|||
const INPUT_EVENT = "input";
|
||||
|
||||
interface ShowHeaders {
|
||||
id: Boolean;
|
||||
owner: Boolean;
|
||||
tags: Boolean;
|
||||
categories: Boolean;
|
||||
tools: Boolean;
|
||||
recipeYield: Boolean;
|
||||
dateAdded: Boolean;
|
||||
id: boolean;
|
||||
owner: boolean;
|
||||
tags: boolean;
|
||||
categories: boolean;
|
||||
tools: boolean;
|
||||
recipeYield: boolean;
|
||||
dateAdded: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -129,7 +129,7 @@ export default defineComponent({
|
|||
// ============
|
||||
// Group Members
|
||||
const api = useUserApi();
|
||||
const members = ref<UserOut[] | null[]>([]);
|
||||
const members = ref<UserOut[]>([]);
|
||||
|
||||
async function refreshMembers() {
|
||||
const { data } = await api.groups.fetchMembers();
|
||||
|
@ -142,10 +142,9 @@ export default defineComponent({
|
|||
refreshMembers();
|
||||
});
|
||||
|
||||
function getMember(id: number) {
|
||||
function getMember(id: string) {
|
||||
if (members.value[0]) {
|
||||
// @ts-ignore
|
||||
return members.value.find((m) => m.id === id).username;
|
||||
return members.value.find((m) => m.id === id)?.username;
|
||||
}
|
||||
|
||||
return "None";
|
||||
|
@ -165,4 +164,4 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, ref, watch } from "@nuxtjs/composition-api";
|
||||
import { defineComponent, toRefs, reactive, ref, watch, useRoute } from "@nuxtjs/composition-api";
|
||||
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||
import { useRecipes, allRecipes, useRecipeSearch } from "~/composables/recipes";
|
||||
import { RecipeSummary } from "~/types/api-types/recipe";
|
||||
|
@ -74,7 +74,7 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
// ===========================================================================
|
||||
// Dialong State Management
|
||||
// Dialog State Management
|
||||
const dialog = ref(false);
|
||||
|
||||
// Reset or Grab Recipes on Change
|
||||
|
@ -89,6 +89,53 @@ export default defineComponent({
|
|||
}
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// Event Handlers
|
||||
|
||||
function selectRecipe() {
|
||||
const recipeCards = document.getElementsByClassName("arrow-nav");
|
||||
if (recipeCards) {
|
||||
if (state.selectedIndex < 0) {
|
||||
state.selectedIndex = -1;
|
||||
document.getElementById("arrow-search")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.selectedIndex >= recipeCards.length) {
|
||||
state.selectedIndex = recipeCards.length - 1;
|
||||
}
|
||||
|
||||
(recipeCards[state.selectedIndex] as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
function onUpDown(e: KeyboardEvent) {
|
||||
if (e.key === "Enter") {
|
||||
console.log(document.activeElement);
|
||||
// (document.activeElement as HTMLElement).click();
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
state.selectedIndex--;
|
||||
} else if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
state.selectedIndex++;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
selectRecipe();
|
||||
}
|
||||
|
||||
watch(dialog, (val) => {
|
||||
if (!val) {
|
||||
document.removeEventListener("keyup", onUpDown);
|
||||
} else {
|
||||
document.addEventListener("keyup", onUpDown);
|
||||
}
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
watch(route, close);
|
||||
|
||||
function open() {
|
||||
dialog.value = true;
|
||||
}
|
||||
|
@ -110,56 +157,6 @@ export default defineComponent({
|
|||
|
||||
return { allRecipes, refreshRecipes, ...toRefs(state), dialog, open, close, handleSelect, search, results };
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
|
||||
computed: {},
|
||||
watch: {
|
||||
$route() {
|
||||
this.dialog = false;
|
||||
},
|
||||
dialog() {
|
||||
if (!this.dialog) {
|
||||
document.removeEventListener("keyup", this.onUpDown);
|
||||
} else {
|
||||
document.addEventListener("keyup", this.onUpDown);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onUpDown(e: KeyboardEvent) {
|
||||
if (e.key === "Enter") {
|
||||
console.log(document.activeElement);
|
||||
// (document.activeElement as HTMLElement).click();
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
this.selectedIndex--;
|
||||
} else if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
this.selectedIndex++;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.selectRecipe();
|
||||
},
|
||||
selectRecipe() {
|
||||
const recipeCards = document.getElementsByClassName("arrow-nav");
|
||||
if (recipeCards) {
|
||||
if (this.selectedIndex < 0) {
|
||||
this.selectedIndex = -1;
|
||||
document.getElementById("arrow-search")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedIndex >= recipeCards.length) {
|
||||
this.selectedIndex = recipeCards.length - 1;
|
||||
}
|
||||
|
||||
(recipeCards[this.selectedIndex] as HTMLElement).focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -167,4 +164,4 @@ export default defineComponent({
|
|||
.scroll {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -20,9 +20,10 @@
|
|||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import {UserOut} from "~/types/api-types/user";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
slug: {
|
||||
|
@ -38,31 +39,29 @@ export default defineComponent({
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
setup(props) {
|
||||
const api = useUserApi();
|
||||
const { $auth } = useContext();
|
||||
|
||||
return { api };
|
||||
},
|
||||
computed: {
|
||||
user() {
|
||||
return this.$auth.user;
|
||||
},
|
||||
isFavorite() {
|
||||
return this.$auth.user.favoriteRecipes.includes(this.slug);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async toggleFavorite() {
|
||||
if (!this.isFavorite) {
|
||||
await this.api.users.addFavorite(this.$auth.user.id, this.slug);
|
||||
// TODO Setup the correct type for $auth.user
|
||||
// See https://github.com/nuxt-community/auth-module/issues/1097
|
||||
const user = computed(() => $auth.user as unknown as UserOut);
|
||||
const isFavorite = computed(() => user.value?.favoriteRecipes?.includes(props.slug));
|
||||
|
||||
async function toggleFavorite() {
|
||||
console.log("Favorited?");
|
||||
if (!isFavorite.value) {
|
||||
await api.users.addFavorite(user.value?.id, props.slug);
|
||||
} else {
|
||||
await this.api.users.removeFavorite(this.$auth.user.id, this.slug);
|
||||
await api.users.removeFavorite(user.value?.id, props.slug);
|
||||
}
|
||||
this.$auth.fetchUser();
|
||||
},
|
||||
$auth.fetchUser();
|
||||
};
|
||||
|
||||
return { isFavorite, toggleFavorite };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</v-card-title>
|
||||
<v-card-text class="mt-n5">
|
||||
<div>
|
||||
<v-text-field v-model="url" :label="$t('general.url')" class="pt-5" clearable :messages="getMessages()">
|
||||
<v-text-field v-model="url" :label="$t('general.url')" class="pt-5" clearable :messages="messages">
|
||||
<template #append-outer>
|
||||
<v-btn class="ml-2" color="primary" :loading="loading" :disabled="!slug" @click="getImageFromURL">
|
||||
{{ $t("general.get") }}
|
||||
|
@ -39,11 +39,13 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
|
||||
const REFRESH_EVENT = "refresh";
|
||||
const UPLOAD_EVENT = "upload";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
slug: {
|
||||
|
@ -51,32 +53,37 @@ export default defineComponent({
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
setup(props, context) {
|
||||
const state = reactive({
|
||||
url: "",
|
||||
loading: false,
|
||||
menu: false,
|
||||
})
|
||||
|
||||
return { api };
|
||||
},
|
||||
data: () => ({
|
||||
url: "",
|
||||
loading: false,
|
||||
menu: false,
|
||||
}),
|
||||
methods: {
|
||||
uploadImage(fileObject) {
|
||||
this.$emit(UPLOAD_EVENT, fileObject);
|
||||
this.menu = false;
|
||||
},
|
||||
async getImageFromURL() {
|
||||
this.loading = true;
|
||||
if (await this.api.recipes.updateImagebyURL(this.slug, this.url)) {
|
||||
this.$emit(REFRESH_EVENT);
|
||||
function uploadImage(fileObject: File) {
|
||||
context.emit(UPLOAD_EVENT, fileObject);
|
||||
state.menu = false;
|
||||
}
|
||||
|
||||
const api = useUserApi();
|
||||
async function getImageFromURL() {
|
||||
state.loading = true;
|
||||
if (await api.recipes.updateImagebyURL(props.slug, state.url)) {
|
||||
context.emit(REFRESH_EVENT);
|
||||
}
|
||||
this.loading = false;
|
||||
this.menu = false;
|
||||
},
|
||||
getMessages() {
|
||||
return this.slug ? [""] : [this.$i18n.t("recipe.save-recipe-before-use")];
|
||||
},
|
||||
state.loading = false;
|
||||
state.menu = false;
|
||||
}
|
||||
|
||||
const { i18n } = useContext();
|
||||
const messages = props.slug ? [""] : [i18n.t("recipe.save-recipe-before-use")];
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
uploadImage,
|
||||
getImageFromURL,
|
||||
messages,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -105,11 +105,12 @@
|
|||
import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api";
|
||||
import { useFoods, useUnits } from "~/composables/recipes";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
type: Object as () => RecipeIngredient,
|
||||
required: true,
|
||||
},
|
||||
disableAmount: {
|
||||
|
@ -157,14 +158,14 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
function handleUnitEnter() {
|
||||
if (value.unit === null || !value.unit.name.includes(unitSearch.value)) {
|
||||
if (value.unit === undefined || !value.unit.name.includes(unitSearch.value)) {
|
||||
console.log("Creating");
|
||||
createAssignUnit();
|
||||
}
|
||||
}
|
||||
|
||||
function handleFoodEnter() {
|
||||
if (value.food === null || !value.food.name.includes(foodSearch.value)) {
|
||||
if (value.food === undefined || !value.food.name.includes(foodSearch.value)) {
|
||||
console.log("Creating");
|
||||
createAssignFood();
|
||||
}
|
||||
|
@ -194,4 +195,4 @@ export default defineComponent({
|
|||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -23,17 +23,20 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
type: Array as () => RecipeIngredient[],
|
||||
default: () => [],
|
||||
},
|
||||
disableAmount: {
|
||||
|
@ -46,6 +49,15 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup(props) {
|
||||
function validateTitle(title?: string) {
|
||||
return !(title === undefined || title === "");
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
checked: props.value.map(() => false),
|
||||
showTitleEditor: computed(() => props.value.map((x) => validateTitle(x.title))),
|
||||
});
|
||||
|
||||
const ingredientCopyText = computed(() => {
|
||||
return props.value
|
||||
.map((ingredient) => {
|
||||
|
@ -54,41 +66,18 @@ export default defineComponent({
|
|||
.join("\n");
|
||||
});
|
||||
|
||||
return { parseIngredientText, ingredientCopyText };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drag: false,
|
||||
checked: [],
|
||||
showTitleEditor: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler() {
|
||||
this.showTitleEditor = this.value.map((x) => this.validateTitle(x.title));
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.checked = this.value.map(() => false);
|
||||
this.showTitleEditor = this.value.map((x) => this.validateTitle(x.title));
|
||||
},
|
||||
methods: {
|
||||
toggleChecked(index) {
|
||||
this.$set(this.checked, index, !this.checked[index]);
|
||||
},
|
||||
function toggleChecked(index: number) {
|
||||
// TODO Find a better way to do this - $set is not available, and
|
||||
// direct array modifications are not propagated for some reason
|
||||
state.checked.splice(index, 1, !state.checked[index]);
|
||||
}
|
||||
|
||||
validateTitle(title) {
|
||||
return !(title === null || title === "");
|
||||
},
|
||||
toggleShowTitle(index) {
|
||||
const newVal = !this.showTitleEditor[index];
|
||||
if (!newVal) {
|
||||
this.value[index].title = "";
|
||||
}
|
||||
this.$set(this.showTitleEditor, index, newVal);
|
||||
},
|
||||
return {
|
||||
...toRefs(state),
|
||||
parseIngredientText,
|
||||
ingredientCopyText,
|
||||
toggleChecked,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -153,7 +153,7 @@ import draggable from "vuedraggable";
|
|||
// @ts-ignore
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { ref, toRefs, reactive, defineComponent, watch, onMounted } from "@nuxtjs/composition-api";
|
||||
import { RecipeStep, IngredientToStepRef, RecipeIngredient } from "~/types/api-types/recipe";
|
||||
import { RecipeStep, IngredientReferences, RecipeIngredient } from "~/types/api-types/recipe";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { uuid4 } from "~/composables/use-utils";
|
||||
|
||||
|
@ -227,14 +227,18 @@ export default defineComponent({
|
|||
state.disabledSteps = [];
|
||||
|
||||
v.forEach((element) => {
|
||||
showTitleEditor.value[element.id] = validateTitle(element.title);
|
||||
if (element.id !== undefined) {
|
||||
showTitleEditor.value[element.id] = validateTitle(element.title);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Eliminate state with an eager call to watcher?
|
||||
onMounted(() => {
|
||||
props.value.forEach((element) => {
|
||||
showTitleEditor.value[element.id] = validateTitle(element.title);
|
||||
if (element.id !== undefined) {
|
||||
showTitleEditor.value[element.id] = validateTitle(element.title);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -268,23 +272,23 @@ export default defineComponent({
|
|||
|
||||
// ===============================================================
|
||||
// Ingredient Linker
|
||||
const activeRefs = ref<String[]>([]);
|
||||
const activeRefs = ref<string[]>([]);
|
||||
const activeIndex = ref(0);
|
||||
const activeText = ref("");
|
||||
|
||||
function openDialog(idx: number, refs: IngredientToStepRef[], text: string) {
|
||||
function openDialog(idx: number, refs: IngredientReferences[], text: string) {
|
||||
setUsedIngredients();
|
||||
activeText.value = text;
|
||||
activeIndex.value = idx;
|
||||
state.dialog = true;
|
||||
activeRefs.value = refs.map((ref) => ref.referenceId);
|
||||
activeRefs.value = refs.map((ref) => ref.referenceId ?? "");
|
||||
}
|
||||
|
||||
function setIngredientIds() {
|
||||
const instruction = props.value[activeIndex.value];
|
||||
instruction.ingredientReferences = activeRefs.value.map((ref) => {
|
||||
return {
|
||||
referenceId: ref as string,
|
||||
referenceId: ref,
|
||||
};
|
||||
});
|
||||
state.dialog = false;
|
||||
|
@ -294,17 +298,19 @@ export default defineComponent({
|
|||
const usedRefs: { [key: string]: boolean } = {};
|
||||
|
||||
props.value.forEach((element) => {
|
||||
element.ingredientReferences.forEach((ref) => {
|
||||
usedRefs[ref.referenceId] = true;
|
||||
element.ingredientReferences?.forEach((ref) => {
|
||||
if (ref.referenceId !== undefined) {
|
||||
usedRefs[ref.referenceId] = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
state.usedIngredients = props.ingredients.filter((ing) => {
|
||||
return ing.referenceId in usedRefs;
|
||||
return ing.referenceId !== undefined && ing.referenceId in usedRefs;
|
||||
});
|
||||
|
||||
state.unusedIngredients = props.ingredients.filter((ing) => {
|
||||
return !(ing.referenceId in usedRefs);
|
||||
return !(ing.referenceId !== undefined && ing.referenceId in usedRefs);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -343,6 +349,10 @@ export default defineComponent({
|
|||
props.ingredients.forEach((ingredient) => {
|
||||
const searchText = parseIngredientText(ingredient, props.disableAmount);
|
||||
|
||||
if (ingredient.referenceId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchText.toLowerCase().includes(" " + word) && !activeRefs.value.includes(ingredient.referenceId)) {
|
||||
console.info("Word Matched", `'${word}'`, ingredient.note);
|
||||
activeRefs.value.push(ingredient.referenceId);
|
||||
|
@ -351,7 +361,7 @@ export default defineComponent({
|
|||
});
|
||||
}
|
||||
|
||||
function getIngredientByRefId(refId: String) {
|
||||
function getIngredientByRefId(refId: string) {
|
||||
const ing = props.ingredients.find((ing) => ing.referenceId === refId) || "";
|
||||
if (ing === "") {
|
||||
return "";
|
||||
|
|
|
@ -31,9 +31,12 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
// @ts-ignore
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
export default {
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
|
@ -48,15 +51,21 @@ export default {
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addNote() {
|
||||
this.value.push({ title: "", text: "" });
|
||||
},
|
||||
removeByIndex(list, index) {
|
||||
setup(props) {
|
||||
function addNote() {
|
||||
props.value.push({ title: "", text: "" });
|
||||
}
|
||||
|
||||
function removeByIndex(list: unknown[], index: number) {
|
||||
list.splice(index, 1);
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
addNote,
|
||||
removeByIndex,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -33,11 +33,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { Nutrition } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
type: Object as () => Nutrition,
|
||||
required: true,
|
||||
},
|
||||
edit: {
|
||||
|
@ -45,59 +48,59 @@ export default {
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
labels: {
|
||||
calories: {
|
||||
label: this.$t("recipe.calories"),
|
||||
suffix: this.$t("recipe.calories-suffix"),
|
||||
},
|
||||
fatContent: {
|
||||
label: this.$t("recipe.fat-content"),
|
||||
suffix: this.$t("recipe.grams"),
|
||||
},
|
||||
fiberContent: {
|
||||
label: this.$t("recipe.fiber-content"),
|
||||
suffix: this.$t("recipe.grams"),
|
||||
},
|
||||
proteinContent: {
|
||||
label: this.$t("recipe.protein-content"),
|
||||
suffix: this.$t("recipe.grams"),
|
||||
},
|
||||
sodiumContent: {
|
||||
label: this.$t("recipe.sodium-content"),
|
||||
suffix: this.$t("recipe.milligrams"),
|
||||
},
|
||||
sugarContent: {
|
||||
label: this.$t("recipe.sugar-content"),
|
||||
suffix: this.$t("recipe.grams"),
|
||||
},
|
||||
carbohydrateContent: {
|
||||
label: this.$t("recipe.carbohydrate-content"),
|
||||
suffix: this.$t("recipe.grams"),
|
||||
},
|
||||
setup(props, context) {
|
||||
const { i18n } = useContext();
|
||||
const labels = {
|
||||
calories: {
|
||||
label: i18n.t("recipe.calories"),
|
||||
suffix: i18n.t("recipe.calories-suffix"),
|
||||
},
|
||||
fatContent: {
|
||||
label: i18n.t("recipe.fat-content"),
|
||||
suffix: i18n.t("recipe.grams"),
|
||||
},
|
||||
fiberContent: {
|
||||
label: i18n.t("recipe.fiber-content"),
|
||||
suffix: i18n.t("recipe.grams"),
|
||||
},
|
||||
proteinContent: {
|
||||
label: i18n.t("recipe.protein-content"),
|
||||
suffix: i18n.t("recipe.grams"),
|
||||
},
|
||||
sodiumContent: {
|
||||
label: i18n.t("recipe.sodium-content"),
|
||||
suffix: i18n.t("recipe.milligrams"),
|
||||
},
|
||||
sugarContent: {
|
||||
label: i18n.t("recipe.sugar-content"),
|
||||
suffix: i18n.t("recipe.grams"),
|
||||
},
|
||||
carbohydrateContent: {
|
||||
label: i18n.t("recipe.carbohydrate-content"),
|
||||
suffix: i18n.t("recipe.grams"),
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showViewer() {
|
||||
return !this.edit && this.valueNotNull;
|
||||
},
|
||||
valueNotNull() {
|
||||
for (const property in this.value) {
|
||||
const valueProperty = this.value[property];
|
||||
const valueNotNull = computed(() => {
|
||||
Object.values(props.value).forEach((valueProperty) => {
|
||||
if (valueProperty && valueProperty !== "") return true;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
methods: {
|
||||
updateValue(key, value) {
|
||||
this.$emit("input", { ...this.value, [key]: value });
|
||||
},
|
||||
const showViewer = computed(() => !props.edit && valueNotNull.value);
|
||||
|
||||
function updateValue(key: number | string, event: Event) {
|
||||
context.emit("input", { ...props.value, [key]: event });
|
||||
}
|
||||
|
||||
return {
|
||||
labels,
|
||||
valueNotNull,
|
||||
showViewer,
|
||||
updateValue
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
@ -25,6 +25,7 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// TODO Remove name prop?
|
||||
name: {
|
||||
type: String,
|
||||
default: "",
|
||||
|
@ -42,36 +43,26 @@ export default defineComponent({
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
setup(props, context) {
|
||||
const { $auth } = useContext();
|
||||
const loggedIn = computed(() => {
|
||||
return $auth.loggedIn;
|
||||
});
|
||||
|
||||
return { api };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rating: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return this.$auth.loggedIn;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.rating = this.value;
|
||||
},
|
||||
methods: {
|
||||
updateRating(val) {
|
||||
if (this.emitOnly) {
|
||||
this.$emit("input", val);
|
||||
const rating = ref(props.value);
|
||||
|
||||
const api = useUserApi();
|
||||
function updateRating(val: number) {
|
||||
if (props.emitOnly) {
|
||||
context.emit("input", val);
|
||||
return;
|
||||
}
|
||||
this.api.recipes.patchOne(this.slug, {
|
||||
name: this.name,
|
||||
slug: this.slug,
|
||||
api.recipes.patchOne(props.slug, {
|
||||
rating: val,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
return { loggedIn, rating, updateRating };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -34,9 +34,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
<script lang="ts">
|
||||
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
|
@ -47,23 +48,23 @@ export default {
|
|||
required: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { i18n } = useContext();
|
||||
const labels = {
|
||||
public: i18n.t("recipe.public-recipe"),
|
||||
showNutrition: i18n.t("recipe.show-nutrition-values"),
|
||||
showAssets: i18n.t("asset.show-assets"),
|
||||
landscapeView: i18n.t("recipe.landscape-view-coming-soon"),
|
||||
disableComments: i18n.t("recipe.disable-comments"),
|
||||
disableAmount: i18n.t("recipe.disable-amount"),
|
||||
locked: "Locked",
|
||||
};
|
||||
|
||||
computed: {
|
||||
labels() {
|
||||
return {
|
||||
public: this.$t("recipe.public-recipe"),
|
||||
showNutrition: this.$t("recipe.show-nutrition-values"),
|
||||
showAssets: this.$t("asset.show-assets"),
|
||||
landscapeView: this.$t("recipe.landscape-view-coming-soon"),
|
||||
disableComments: this.$t("recipe.disable-comments"),
|
||||
disableAmount: this.$t("recipe.disable-amount"),
|
||||
locked: "Locked",
|
||||
};
|
||||
},
|
||||
return {
|
||||
labels,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
prepTime: {
|
||||
type: String,
|
||||
|
@ -26,29 +28,39 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showCards() {
|
||||
return [this.prepTime, this.totalTime, this.performTime].some((x) => !this.isEmpty(x));
|
||||
},
|
||||
allTimes() {
|
||||
return [this.validateTotalTime, this.validatePrepTime, this.validatePerformTime].filter((x) => x !== null);
|
||||
},
|
||||
validateTotalTime() {
|
||||
return !this.isEmpty(this.totalTime) ? { name: this.$t("recipe.total-time"), value: this.totalTime } : null;
|
||||
},
|
||||
validatePrepTime() {
|
||||
return !this.isEmpty(this.prepTime) ? { name: this.$t("recipe.prep-time"), value: this.prepTime } : null;
|
||||
},
|
||||
validatePerformTime() {
|
||||
return !this.isEmpty(this.performTime) ? { name: this.$t("recipe.perform-time"), value: this.performTime } : null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isEmpty(str) {
|
||||
setup(props) {
|
||||
const { i18n } = useContext();
|
||||
|
||||
function isEmpty(str: string | null) {
|
||||
return !str || str.length === 0;
|
||||
},
|
||||
}
|
||||
|
||||
const showCards = computed(() => {
|
||||
return [props.prepTime, props.totalTime, props.performTime].some((x) => !isEmpty(x));
|
||||
});
|
||||
|
||||
const validateTotalTime = computed(() => {
|
||||
return !isEmpty(props.totalTime) ? { name: i18n.t("recipe.total-time"), value: props.totalTime } : null;
|
||||
});
|
||||
|
||||
const validatePrepTime = computed(() => {
|
||||
return !isEmpty(props.prepTime) ? { name: i18n.t("recipe.prep-time"), value: props.prepTime } : null;
|
||||
});
|
||||
|
||||
const validatePerformTime = computed(() => {
|
||||
return !isEmpty(props.performTime) ? { name: i18n.t("recipe.perform-time"), value: props.performTime } : null;
|
||||
});
|
||||
|
||||
const allTimes = computed(() => {
|
||||
return [validateTotalTime.value, validatePrepTime.value, validatePerformTime.value].filter((x) => x !== null);
|
||||
});
|
||||
|
||||
return {
|
||||
showCards,
|
||||
allTimes,
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, useContext, computed } from "@nuxtjs/composition-api";
|
||||
import { UserOut } from "~/types/api-types/user";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
@ -33,7 +34,9 @@ export default defineComponent({
|
|||
const { $auth } = useContext();
|
||||
|
||||
const imageURL = computed(() => {
|
||||
const key = $auth?.user?.cacheKey || "";
|
||||
// TODO Setup correct user type for $auth.user
|
||||
const user = $auth.user as unknown as (UserOut | null);
|
||||
const key = user?.cacheKey ?? "";
|
||||
return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
|
||||
});
|
||||
|
||||
|
@ -43,4 +46,4 @@ export default defineComponent({
|
|||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -46,10 +46,11 @@
|
|||
</template>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||
import { defineComponent, onBeforeUnmount, onMounted, ref } from "@nuxtjs/composition-api";
|
||||
import RecipeDialogSearch from "~/components/Domain/Recipe/RecipeDialogSearch.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: { RecipeDialogSearch },
|
||||
props: {
|
||||
|
@ -59,36 +60,32 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup() {
|
||||
const domSearchDialog = ref(null);
|
||||
const domSearchDialog = ref<InstanceType<typeof RecipeDialogSearch> | null>(null);
|
||||
|
||||
function activateSearch() {
|
||||
// @ts-ignore
|
||||
domSearchDialog?.value?.open();
|
||||
domSearchDialog.value?.open();
|
||||
}
|
||||
|
||||
function handleKeyEvent(e: KeyboardEvent) {
|
||||
const activeTag = document.activeElement?.tagName;
|
||||
if (e.key === "/" && activeTag !== "INPUT" && activeTag !== "TEXTAREA") {
|
||||
e.preventDefault();
|
||||
activateSearch();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("keydown", handleKeyEvent);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("keydown", handleKeyEvent);
|
||||
});
|
||||
|
||||
return {
|
||||
activateSearch,
|
||||
domSearchDialog,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("keyup", this.handleKeyEvent);
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener("keyup", this.handleKeyEvent);
|
||||
},
|
||||
methods: {
|
||||
handleKeyEvent(e: any) {
|
||||
if (
|
||||
e.key === "/" &&
|
||||
// @ts-ignore
|
||||
!document.activeElement.id.startsWith("input")
|
||||
) {
|
||||
e.preventDefault();
|
||||
this.activateSearch();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -32,16 +32,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||
import { toastAlert, toastLoading } from "~/composables/use-toast";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
return { toastAlert, toastLoading };
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
switch (this.toastAlert.color) {
|
||||
const icon = computed(() => {
|
||||
switch (toastAlert.color) {
|
||||
case "error":
|
||||
return "mdi-alert";
|
||||
case "success":
|
||||
|
@ -51,7 +49,9 @@ export default {
|
|||
default:
|
||||
return "mdi-alert";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { icon, toastAlert, toastLoading };
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -34,8 +34,11 @@
|
|||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||
import { VTooltip } from "~/types/vuetify";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
copyText: {
|
||||
type: String,
|
||||
|
@ -54,30 +57,34 @@ export default {
|
|||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
setup(props) {
|
||||
const show = ref(false);
|
||||
const copyToolTip = ref<VTooltip | null>(null);
|
||||
|
||||
methods: {
|
||||
toggleBlur() {
|
||||
this.$refs.copyToolTip.deactivate();
|
||||
},
|
||||
textToClipboard() {
|
||||
this.show = true;
|
||||
const copyText = this.copyText;
|
||||
function toggleBlur() {
|
||||
copyToolTip.value?.deactivate();
|
||||
}
|
||||
|
||||
function textToClipboard() {
|
||||
show.value = true;
|
||||
const copyText = props.copyText;
|
||||
navigator.clipboard.writeText(copyText).then(
|
||||
() => console.log(`Copied\n${copyText}`),
|
||||
() => console.log(`Copied Failed\n${copyText}`)
|
||||
);
|
||||
setTimeout(() => {
|
||||
this.toggleBlur();
|
||||
toggleBlur();
|
||||
}, 500);
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
show,
|
||||
copyToolTip,
|
||||
textToClipboard,
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -10,10 +10,13 @@
|
|||
</v-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
|
||||
const UPLOAD_EVENT = "uploaded";
|
||||
export default {
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
small: {
|
||||
type: Boolean,
|
||||
|
@ -48,65 +51,70 @@ export default {
|
|||
default: "",
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
setup(props, context) {
|
||||
const file = ref<File | null>(null);
|
||||
const uploader = ref<HTMLInputElement | null>(null);
|
||||
const isSelecting = ref(false);
|
||||
|
||||
const { i18n, $globals } = useContext();
|
||||
const effIcon = props.icon ? props.icon : $globals.icons.upload;
|
||||
|
||||
const defaultText = i18n.t("general.upload");
|
||||
|
||||
const api = useUserApi();
|
||||
async function upload() {
|
||||
if (file.value != null) {
|
||||
isSelecting.value = true;
|
||||
|
||||
return { api };
|
||||
},
|
||||
data: () => ({
|
||||
file: null,
|
||||
isSelecting: false,
|
||||
}),
|
||||
|
||||
computed: {
|
||||
effIcon() {
|
||||
return this.icon ? this.icon : this.$globals.icons.upload;
|
||||
},
|
||||
defaultText() {
|
||||
return this.$t("general.upload");
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async upload() {
|
||||
if (this.file != null) {
|
||||
this.isSelecting = true;
|
||||
|
||||
if (!this.post) {
|
||||
this.$emit(UPLOAD_EVENT, this.file);
|
||||
this.isSelecting = false;
|
||||
if (!props.post) {
|
||||
context.emit(UPLOAD_EVENT, file.value);
|
||||
isSelecting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append(this.fileName, this.file);
|
||||
formData.append(props.fileName, file.value);
|
||||
|
||||
const response = await this.api.upload.file(this.url, formData);
|
||||
const response = await api.upload.file(props.url, formData);
|
||||
|
||||
if (response) {
|
||||
this.$emit(UPLOAD_EVENT, response);
|
||||
context.emit(UPLOAD_EVENT, response);
|
||||
}
|
||||
this.isSelecting = false;
|
||||
isSelecting.value = false;
|
||||
}
|
||||
},
|
||||
onButtonClick() {
|
||||
this.isSelecting = true;
|
||||
}
|
||||
|
||||
function onFileChanged(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.files !== null && target.files.length > 0 && file.value !== null) {
|
||||
file.value = target.files[0];
|
||||
upload();
|
||||
}
|
||||
}
|
||||
|
||||
function onButtonClick() {
|
||||
isSelecting.value = true;
|
||||
window.addEventListener(
|
||||
"focus",
|
||||
() => {
|
||||
this.isSelecting = false;
|
||||
isSelecting.value = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
uploader.value?.click();
|
||||
}
|
||||
|
||||
this.$refs.uploader.click();
|
||||
},
|
||||
onFileChanged(e) {
|
||||
this.file = e.target.files[0];
|
||||
this.upload();
|
||||
},
|
||||
return {
|
||||
file,
|
||||
uploader,
|
||||
isSelecting,
|
||||
effIcon,
|
||||
defaultText,
|
||||
onFileChanged,
|
||||
onButtonClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
@ -40,15 +42,15 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
size() {
|
||||
if (this.small) {
|
||||
setup(props) {
|
||||
const size = computed(() => {
|
||||
if (props.small) {
|
||||
return {
|
||||
width: 2,
|
||||
icon: 30,
|
||||
size: 50,
|
||||
};
|
||||
} else if (this.large) {
|
||||
} else if (props.large) {
|
||||
return {
|
||||
width: 4,
|
||||
icon: 120,
|
||||
|
@ -60,10 +62,15 @@ export default {
|
|||
icon: 75,
|
||||
size: 125,
|
||||
};
|
||||
},
|
||||
waitingText() {
|
||||
return this.$t("general.loading-recipes");
|
||||
},
|
||||
});
|
||||
|
||||
const { i18n } = useContext();
|
||||
const waitingText = i18n.t("general.loading-recipes");
|
||||
|
||||
return {
|
||||
size,
|
||||
waitingText,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -137,14 +137,15 @@
|
|||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "@nuxtjs/composition-api";
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||
import { validators } from "@/composables/use-validators";
|
||||
import { fieldTypes } from "@/composables/forms";
|
||||
import { AutoFormItems } from "~/types/auto-forms";
|
||||
|
||||
const BLUR_EVENT = "blur";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "AutoForm",
|
||||
props: {
|
||||
value: {
|
||||
|
@ -157,7 +158,7 @@ export default {
|
|||
},
|
||||
items: {
|
||||
default: null,
|
||||
type: Array,
|
||||
type: Array as () => AutoFormItems,
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
|
@ -165,7 +166,7 @@ export default {
|
|||
},
|
||||
globalRules: {
|
||||
default: null,
|
||||
type: Array,
|
||||
type: Array as () => string[],
|
||||
},
|
||||
color: {
|
||||
default: null,
|
||||
|
@ -176,94 +177,53 @@ export default {
|
|||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const menu = ref({});
|
||||
setup(props, context) {
|
||||
function rulesByKey(keys?: string[] | null) {
|
||||
if (keys === undefined || keys === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
menu,
|
||||
fieldTypes,
|
||||
validators,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
defaultRules() {
|
||||
return this.rulesByKey(this.globalRules);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
items: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
// Initialize Value Object to Obtain all keys
|
||||
if (!val) {
|
||||
return;
|
||||
const list = [] as ((v: string) => (boolean | string))[];
|
||||
keys.forEach((key) => {
|
||||
if (key in validators) {
|
||||
list.push(validators[key]);
|
||||
}
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
try {
|
||||
if (this.value[val[i].varName]) {
|
||||
continue;
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
if (val[i].type === "text" || val[i].type === "textarea") {
|
||||
this.$set(this.value, val[i].varName, "");
|
||||
} else if (val[i].type === "select") {
|
||||
if (!val[i].options[0]) {
|
||||
continue;
|
||||
}
|
||||
const defaultRules = computed(() => rulesByKey(props.globalRules));
|
||||
|
||||
this.$set(this.value, val[i].varName, val[i].options[0].value);
|
||||
} else if (val[i].type === "list") {
|
||||
this.$set(this.value, val[i].varName, []);
|
||||
} else if (val[i].type === "object") {
|
||||
this.$set(this.value, val[i].varName, {});
|
||||
} else if (val[i].type === "color") {
|
||||
this.$set(this.value, val[i].varName, "");
|
||||
this.$set(this.menu, val[i].varName, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeByIndex(list, index) {
|
||||
function removeByIndex(list: never[], index: number) {
|
||||
// Removes the item at the index
|
||||
list.splice(index, 1);
|
||||
},
|
||||
getTemplate(item) {
|
||||
const obj = {};
|
||||
}
|
||||
|
||||
function getTemplate(item: AutoFormItems) {
|
||||
const obj = {} as { [key: string]: string };
|
||||
|
||||
item.forEach((field) => {
|
||||
obj[field.varName] = "";
|
||||
});
|
||||
|
||||
return obj;
|
||||
},
|
||||
rulesByKey(keys) {
|
||||
const list = [];
|
||||
}
|
||||
|
||||
if (keys === undefined) {
|
||||
return list;
|
||||
}
|
||||
if (keys === null) {
|
||||
return list;
|
||||
}
|
||||
if (keys === list) {
|
||||
return list;
|
||||
}
|
||||
function emitBlur() {
|
||||
context.emit(BLUR_EVENT, props.value);
|
||||
}
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (key in this.validators) {
|
||||
list.push(this.validators[key]);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
},
|
||||
emitBlur() {
|
||||
this.$emit(BLUR_EVENT, this.value);
|
||||
},
|
||||
return {
|
||||
rulesByKey,
|
||||
defaultRules,
|
||||
removeByIndex,
|
||||
getTemplate,
|
||||
emitBlur,
|
||||
fieldTypes,
|
||||
validators,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
<template>
|
||||
<v-card :color="color" :dark="dark" flat :width="width" class="my-2">
|
||||
<v-row>
|
||||
<v-col v-for="(inputField, index) in items" :key="index" class="py-0" cols="12" sm="12">
|
||||
<v-divider v-if="inputField.section" class="my-2" />
|
||||
<v-card-title v-if="inputField.section" class="pl-0">
|
||||
{{ inputField.section }}
|
||||
</v-card-title>
|
||||
<v-card-text v-if="inputField.sectionDetails" class="pl-0 mt-0 pt-0">
|
||||
{{ inputField.sectionDetails }}
|
||||
</v-card-text>
|
||||
|
||||
<!-- Check Box -->
|
||||
<v-checkbox
|
||||
v-if="inputField.type === fieldTypes.BOOLEAN"
|
||||
v-model="value[inputField.varName]"
|
||||
class="my-0 py-0"
|
||||
:label="inputField.label"
|
||||
:name="inputField.varName"
|
||||
:hint="inputField.hint || ''"
|
||||
@change="emitBlur"
|
||||
/>
|
||||
|
||||
<!-- Text Field -->
|
||||
<v-text-field
|
||||
v-else-if="inputField.type === fieldTypes.TEXT"
|
||||
v-model="value[inputField.varName]"
|
||||
:readonly="inputField.fixed && updateMode"
|
||||
filled
|
||||
rounded
|
||||
class="rounded-lg"
|
||||
dense
|
||||
:label="inputField.label"
|
||||
:name="inputField.varName"
|
||||
:hint="inputField.hint || ''"
|
||||
:rules="[...rulesByKey(inputField.rules), ...defaultRules]"
|
||||
lazy-validation
|
||||
@blur="emitBlur"
|
||||
/>
|
||||
|
||||
<!-- Text Area -->
|
||||
<v-textarea
|
||||
v-else-if="inputField.type === fieldTypes.TEXT_AREA"
|
||||
v-model="value[inputField.varName]"
|
||||
:readonly="inputField.fixed && updateMode"
|
||||
filled
|
||||
rounded
|
||||
class="rounded-lg"
|
||||
rows="3"
|
||||
auto-grow
|
||||
dense
|
||||
:label="inputField.label"
|
||||
:name="inputField.varName"
|
||||
:hint="inputField.hint || ''"
|
||||
:rules="[...rulesByKey(inputField.rules), ...defaultRules]"
|
||||
lazy-validation
|
||||
@blur="emitBlur"
|
||||
/>
|
||||
|
||||
<!-- Option Select -->
|
||||
<v-select
|
||||
v-else-if="inputField.type === fieldTypes.SELECT"
|
||||
v-model="value[inputField.varName]"
|
||||
:readonly="inputField.fixed && updateMode"
|
||||
filled
|
||||
rounded
|
||||
class="rounded-lg"
|
||||
:prepend-icon="inputField.icons ? value[inputField.varName] : null"
|
||||
:label="inputField.label"
|
||||
:name="inputField.varName"
|
||||
:items="inputField.options"
|
||||
:return-object="false"
|
||||
lazy-validation
|
||||
@blur="emitBlur"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ item.text }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-select>
|
||||
|
||||
<!-- Color Picker -->
|
||||
<div v-else-if="inputField.type === fieldTypes.COLOR" class="d-flex" style="width: 100%">
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ on }">
|
||||
<v-btn class="my-2 ml-auto" style="min-width: 200px" :color="value[inputField.varName]" dark v-on="on">
|
||||
{{ inputField.label }}
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-color-picker
|
||||
v-model="value[inputField.varName]"
|
||||
value="#7417BE"
|
||||
hide-canvas
|
||||
hide-inputs
|
||||
show-swatches
|
||||
class="mx-auto"
|
||||
@input="emitBlur"
|
||||
/>
|
||||
</v-menu>
|
||||
</div>
|
||||
|
||||
<div v-else-if="inputField.type === fieldTypes.OBJECT">
|
||||
<base-auto-form
|
||||
v-model="value[inputField.varName]"
|
||||
:color="color"
|
||||
:items="inputField.items"
|
||||
@blur="emitBlur"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- List Type -->
|
||||
<div v-else-if="inputField.type === fieldTypes.LIST">
|
||||
<div v-for="(item, idx) in value[inputField.varName]" :key="idx">
|
||||
<p>
|
||||
{{ inputField.label }} {{ idx + 1 }}
|
||||
<span>
|
||||
<BaseButton class="ml-5" x-small delete @click="removeByIndex(value[inputField.varName], idx)" />
|
||||
</span>
|
||||
</p>
|
||||
<v-divider class="mb-5 mx-2" />
|
||||
<base-auto-form
|
||||
v-model="value[inputField.varName][idx]"
|
||||
:color="color"
|
||||
:items="inputField.items"
|
||||
@blur="emitBlur"
|
||||
/>
|
||||
</div>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<BaseButton small @click="value[inputField.varName].push(getTemplate(inputField.items))"> New </BaseButton>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "@nuxtjs/composition-api";
|
||||
import { validators } from "@/composables/use-validators";
|
||||
import { fieldTypes } from "@/composables/forms";
|
||||
|
||||
const BLUR_EVENT = "blur";
|
||||
|
||||
export default {
|
||||
name: "BaseAutoForm",
|
||||
props: {
|
||||
value: {
|
||||
default: null,
|
||||
type: [Object, Array],
|
||||
},
|
||||
updateMode: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
items: {
|
||||
default: null,
|
||||
type: Array,
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: "max",
|
||||
},
|
||||
globalRules: {
|
||||
default: null,
|
||||
type: Array,
|
||||
},
|
||||
color: {
|
||||
default: null,
|
||||
type: String,
|
||||
},
|
||||
dark: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const menu = ref({});
|
||||
|
||||
return {
|
||||
menu,
|
||||
fieldTypes,
|
||||
validators,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
defaultRules() {
|
||||
return this.rulesByKey(this.globalRules);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
items: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
// Initialize Value Object to Obtain all keys
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
try {
|
||||
if (this.value[val[i].varName]) {
|
||||
continue;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (val[i].type === "text" || val[i].type === "textarea") {
|
||||
this.$set(this.value, val[i].varName, "");
|
||||
} else if (val[i].type === "select") {
|
||||
if (!val[i].options[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.$set(this.value, val[i].varName, val[i].options[0].value);
|
||||
} else if (val[i].type === "list") {
|
||||
this.$set(this.value, val[i].varName, []);
|
||||
} else if (val[i].type === "object") {
|
||||
this.$set(this.value, val[i].varName, {});
|
||||
} else if (val[i].type === "color") {
|
||||
this.$set(this.value, val[i].varName, "");
|
||||
this.$set(this.menu, val[i].varName, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeByIndex(list, index) {
|
||||
// Removes the item at the index
|
||||
list.splice(index, 1);
|
||||
},
|
||||
getTemplate(item) {
|
||||
const obj = {};
|
||||
|
||||
item.forEach((field) => {
|
||||
obj[field.varName] = "";
|
||||
});
|
||||
|
||||
return obj;
|
||||
},
|
||||
rulesByKey(keys) {
|
||||
const list = [];
|
||||
|
||||
if (keys === undefined) {
|
||||
return list;
|
||||
}
|
||||
if (keys === null) {
|
||||
return list;
|
||||
}
|
||||
if (keys === list) {
|
||||
return list;
|
||||
}
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (key in this.validators) {
|
||||
list.push(this.validators[key]);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
},
|
||||
emitBlur() {
|
||||
this.$emit(BLUR_EVENT, this.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -28,9 +28,11 @@
|
|||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
export default {
|
||||
|
||||
export default defineComponent({
|
||||
name: "BaseButton",
|
||||
props: {
|
||||
// Types
|
||||
|
@ -106,103 +108,99 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const api = useUserApi();
|
||||
|
||||
return { api };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
buttonOptions: {
|
||||
create: {
|
||||
text: "Create",
|
||||
icon: this.$globals.icons.createAlt,
|
||||
color: "success",
|
||||
},
|
||||
update: {
|
||||
text: "Update",
|
||||
icon: this.$globals.icons.edit,
|
||||
color: "success",
|
||||
},
|
||||
save: {
|
||||
text: "Save",
|
||||
icon: this.$globals.icons.save,
|
||||
color: "success",
|
||||
},
|
||||
edit: {
|
||||
text: "Edit",
|
||||
icon: this.$globals.icons.edit,
|
||||
color: "info",
|
||||
},
|
||||
delete: {
|
||||
text: "Delete",
|
||||
icon: this.$globals.icons.delete,
|
||||
color: "error",
|
||||
},
|
||||
cancel: {
|
||||
text: "Cancel",
|
||||
icon: this.$globals.icons.close,
|
||||
color: "grey",
|
||||
},
|
||||
download: {
|
||||
text: "Download",
|
||||
icon: this.$globals.icons.download,
|
||||
color: "info",
|
||||
},
|
||||
setup(props) {
|
||||
const { $globals } = useContext();
|
||||
const buttonOptions = {
|
||||
create: {
|
||||
text: "Create",
|
||||
icon: $globals.icons.createAlt,
|
||||
color: "success",
|
||||
},
|
||||
buttonStyles: {
|
||||
defaults: {
|
||||
text: false,
|
||||
outlined: false,
|
||||
},
|
||||
secondary: {
|
||||
text: false,
|
||||
outlined: true,
|
||||
},
|
||||
minor: {
|
||||
outlined: false,
|
||||
text: true,
|
||||
},
|
||||
update: {
|
||||
text: "Update",
|
||||
icon: $globals.icons.edit,
|
||||
color: "success",
|
||||
},
|
||||
save: {
|
||||
text: "Save",
|
||||
icon: $globals.icons.save,
|
||||
color: "success",
|
||||
},
|
||||
edit: {
|
||||
text: "Edit",
|
||||
icon: $globals.icons.edit,
|
||||
color: "info",
|
||||
},
|
||||
delete: {
|
||||
text: "Delete",
|
||||
icon: $globals.icons.delete,
|
||||
color: "error",
|
||||
},
|
||||
cancel: {
|
||||
text: "Cancel",
|
||||
icon: $globals.icons.close,
|
||||
color: "grey",
|
||||
},
|
||||
download: {
|
||||
text: "Download",
|
||||
icon: $globals.icons.download,
|
||||
color: "info",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
btnAttrs() {
|
||||
if (this.delete) {
|
||||
return this.buttonOptions.delete;
|
||||
} else if (this.update) {
|
||||
return this.buttonOptions.update;
|
||||
} else if (this.edit) {
|
||||
return this.buttonOptions.edit;
|
||||
} else if (this.cancel) {
|
||||
this.setMinor();
|
||||
return this.buttonOptions.cancel;
|
||||
} else if (this.save) {
|
||||
return this.buttonOptions.save;
|
||||
} else if (this.download) {
|
||||
return this.buttonOptions.download;
|
||||
|
||||
const btnAttrs = computed(() => {
|
||||
if (props.delete) {
|
||||
return buttonOptions.delete;
|
||||
} else if (props.update) {
|
||||
return buttonOptions.update;
|
||||
} else if (props.edit) {
|
||||
return buttonOptions.edit;
|
||||
} else if (props.cancel) {
|
||||
return buttonOptions.cancel;
|
||||
} else if (props.save) {
|
||||
return buttonOptions.save;
|
||||
} else if (props.download) {
|
||||
return buttonOptions.download;
|
||||
}
|
||||
return this.buttonOptions.create;
|
||||
},
|
||||
btnStyle() {
|
||||
if (this.secondary) {
|
||||
return this.buttonStyles.secondary;
|
||||
} else if (this.minor) {
|
||||
return this.buttonStyles.minor;
|
||||
return buttonOptions.create;
|
||||
});
|
||||
|
||||
const buttonStyles = {
|
||||
defaults: {
|
||||
text: false,
|
||||
outlined: false,
|
||||
},
|
||||
secondary: {
|
||||
text: false,
|
||||
outlined: true,
|
||||
},
|
||||
minor: {
|
||||
text: true,
|
||||
outlined: false,
|
||||
},
|
||||
};
|
||||
|
||||
const btnStyle = computed(() => {
|
||||
if (props.secondary) {
|
||||
return buttonStyles.secondary;
|
||||
} else if (props.minor || props.cancel) {
|
||||
return buttonStyles.minor;
|
||||
}
|
||||
return this.buttonStyles.defaults;
|
||||
},
|
||||
return buttonStyles.defaults;
|
||||
});
|
||||
|
||||
|
||||
const api = useUserApi();
|
||||
function downloadFile() {
|
||||
api.utils.download(props.downloadUrl);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
btnAttrs,
|
||||
btnStyle,
|
||||
downloadFile,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setMinor() {
|
||||
this.buttonStyles.defaults = this.buttonStyles.minor;
|
||||
},
|
||||
setSecondary() {
|
||||
this.buttonStyles.defaults = this.buttonStyles.secondary;
|
||||
},
|
||||
downloadFile() {
|
||||
this.api.utils.download(this.downloadUrl);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -23,8 +23,10 @@
|
|||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
@ -39,5 +41,5 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<h3>{{ buttonText }}</h3>
|
||||
</div>
|
||||
<v-text-field v-model="color" hide-details class="ma-0 pa-0" solo>
|
||||
<template #append>
|
||||
<v-menu v-model="menu" top nudge-bottom="105" nudge-left="16" :close-on-content-click="false">
|
||||
<template #activator="{ on }">
|
||||
<div :style="swatchStyle" swatches-max-height="300" v-on="on" />
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text class="pa-0">
|
||||
<v-color-picker v-model="color" flat mode="hexa" show-swatches />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: "Choose a color",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "#ff0000",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
swatches: false,
|
||||
color: this.value || "#1976D2",
|
||||
mask: "!#XXXXXXXX",
|
||||
menu: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
swatchStyle() {
|
||||
const { value, menu } = this;
|
||||
return {
|
||||
backgroundColor: value,
|
||||
cursor: "pointer",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
borderRadius: menu ? "50%" : "4px",
|
||||
transition: "border-radius 200ms ease-in-out",
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
color() {
|
||||
this.updateColor();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateColor() {
|
||||
this.$emit("input", this.color);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -110,7 +110,7 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
const dialog = computed<Boolean>({
|
||||
const dialog = computed<boolean>({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
|
@ -129,12 +129,9 @@ export default defineComponent({
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
determineClose(): Boolean {
|
||||
determineClose(): boolean {
|
||||
return this.submitted && !this.loading && !this.keepOpen;
|
||||
},
|
||||
displayicon(): Boolean {
|
||||
return this.icon || this.$globals.icons.user;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
determineClose() {
|
||||
|
@ -181,4 +178,4 @@ export default defineComponent({
|
|||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
<v-divider :width="width" :class="color" :style="`border-width: ${thickness} !important`" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
width: {
|
||||
type: String,
|
||||
|
@ -18,5 +20,5 @@ export default {
|
|||
default: "accent",
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -17,13 +17,15 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
divider: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
w<template>
|
||||
<template>
|
||||
<v-card v-bind="$attrs" :class="classes" class="v-card--material pa-3">
|
||||
<div class="d-flex grow flex-wrap">
|
||||
<slot name="avatar">
|
||||
|
@ -40,8 +40,10 @@ w<template>
|
|||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MaterialCard",
|
||||
|
||||
props: {
|
||||
|
@ -70,22 +72,25 @@ export default {
|
|||
default: "",
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { $vuetify } = useContext();
|
||||
|
||||
computed: {
|
||||
classes() {
|
||||
const hasHeading = computed(() => false);
|
||||
const hasAltHeading = computed(() => false);
|
||||
const classes = computed(() => {
|
||||
return {
|
||||
"v-card--material--has-heading": this.hasHeading,
|
||||
"mt-3": this.$vuetify.breakpoint.name === "xs" || this.$vuetify.breakpoint.name === "sm",
|
||||
"v-card--material--has-heading": hasHeading,
|
||||
"mt-3": $vuetify.breakpoint.name === "xs" || $vuetify.breakpoint.name === "sm",
|
||||
};
|
||||
},
|
||||
hasHeading() {
|
||||
return false;
|
||||
},
|
||||
hasAltHeading() {
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
hasHeading,
|
||||
hasAltHeading,
|
||||
classes,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
></VJsoneditor>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
// @ts-ignore
|
||||
import VJsoneditor from "v-jsoneditor";
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: { VJsoneditor },
|
||||
props: {
|
||||
value: {
|
||||
|
@ -23,6 +25,6 @@ export default {
|
|||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ export default defineComponent({
|
|||
{ text: "Delete", value: "actions" },
|
||||
];
|
||||
|
||||
function handleRowClick(item: any) {
|
||||
function handleRowClick(item: ReportSummary) {
|
||||
router.push("/user/group/data/reports/" + item.id);
|
||||
}
|
||||
|
||||
|
@ -70,4 +70,4 @@ export default defineComponent({
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
</style>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue