mirror of
https://github.com/hay-kot/mealie.git
synced 2025-07-15 01:23:57 -07: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
|
@ -2,7 +2,7 @@ from pathlib import Path
|
||||||
|
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
template = """// This Code is auto generated by gen_global_componenets.py
|
template = """// This Code is auto generated by gen_global_components.py
|
||||||
{% for name in global %} import {{ name }} from "@/components/global/{{ name }}.vue";
|
{% for name in global %} import {{ name }} from "@/components/global/{{ name }}.vue";
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for name in layout %} import {{ name }} from "@/components/layout/{{ name }}.vue";
|
{% for name in layout %} import {{ name }} from "@/components/layout/{{ name }}.vue";
|
|
@ -5,7 +5,7 @@ from pydantic2ts import generate_typescript_defs
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
|
|
||||||
PROJECT_DIR = Path(__file__).parent.parent.parent
|
PROJECT_DIR = Path(__file__).parent.parent.parent
|
||||||
SCHEMA_PATH = Path("/Users/hayden/Projects/Vue/mealie/mealie/schema/")
|
SCHEMA_PATH = PROJECT_DIR / "mealie" / "schema"
|
||||||
|
|
||||||
TYPES_DIR = CWD / "output" / "types" / "api-types"
|
TYPES_DIR = CWD / "output" / "types" / "api-types"
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,26 @@ module.exports = {
|
||||||
browser: true,
|
browser: true,
|
||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
|
parser: "vue-eslint-parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
requireConfigFile: false,
|
requireConfigFile: false,
|
||||||
|
tsConfigRootDir: __dirname,
|
||||||
|
project: ["./tsconfig.json"],
|
||||||
|
extraFileExtensions: [".vue"],
|
||||||
},
|
},
|
||||||
extends: ["@nuxtjs/eslint-config-typescript", "plugin:nuxt/recommended", "prettier"],
|
extends: [
|
||||||
|
"@nuxtjs/eslint-config-typescript",
|
||||||
|
"plugin:nuxt/recommended",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
// "plugin:prettier/recommended",
|
||||||
|
"prettier",
|
||||||
|
],
|
||||||
|
// Re-add once we use nuxt bridge
|
||||||
|
// See https://v3.nuxtjs.org/getting-started/bridge#update-nuxtconfig
|
||||||
|
ignorePatterns: ["nuxt.config.js"],
|
||||||
plugins: ["prettier"],
|
plugins: ["prettier"],
|
||||||
// add your custom rules here
|
// add your custom rules here
|
||||||
rules: {
|
rules: {
|
||||||
|
@ -27,5 +42,13 @@ module.exports = {
|
||||||
allowModifiers: true,
|
allowModifiers: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
// TODO Gradually activate all rules
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,16 +10,6 @@ export interface CrudAPIInterface {
|
||||||
// Methods
|
// Methods
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CrudAPIMethodsInterface {
|
|
||||||
// CRUD Methods
|
|
||||||
getAll(): any;
|
|
||||||
createOne(): any;
|
|
||||||
getOne(): any;
|
|
||||||
updateOne(): any;
|
|
||||||
patchOne(): any;
|
|
||||||
deleteOne(): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class BaseAPI {
|
export abstract class BaseAPI {
|
||||||
requests: ApiRequestInstance;
|
requests: ApiRequestInstance;
|
||||||
|
|
||||||
|
@ -50,8 +40,8 @@ export abstract class BaseCRUDAPI<T, U> extends BaseAPI implements CrudAPIInterf
|
||||||
return await this.requests.put<T>(this.itemRoute(itemId), payload);
|
return await this.requests.put<T>(this.itemRoute(itemId), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
async patchOne(itemId: string, payload: T) {
|
async patchOne(itemId: string, payload: Partial<T>) {
|
||||||
return await this.requests.patch(this.itemRoute(itemId), payload);
|
return await this.requests.patch<T>(this.itemRoute(itemId), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOne(itemId: string | number) {
|
async deleteOne(itemId: string | number) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ export class AdminGroupsApi extends BaseCRUDAPI<GroupRead, GroupCreate> {
|
||||||
itemRoute = routes.adminUsersId;
|
itemRoute = routes.adminUsersId;
|
||||||
|
|
||||||
async updateOne(id: number, payload: AdminGroupUpdate) {
|
async updateOne(id: number, payload: AdminGroupUpdate) {
|
||||||
return await this.requests.put<GroupRead>(this.itemRoute(id), payload);
|
// TODO: This should probably be a patch request, which isn't offered by the API currently
|
||||||
|
return await this.requests.put<GroupRead, AdminGroupUpdate>(this.itemRoute(id), payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,10 +59,10 @@ export interface Invitation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetPermissions {
|
export interface SetPermissions {
|
||||||
userId: number;
|
userId: string;
|
||||||
canInvite: boolean;
|
canInvite?: boolean;
|
||||||
canManage: boolean;
|
canManage?: boolean;
|
||||||
canOrganize: boolean;
|
canOrganize?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
|
export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
|
||||||
|
@ -87,7 +87,8 @@ export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPreferences(payload: UpdatePreferences) {
|
async setPreferences(payload: UpdatePreferences) {
|
||||||
return await this.requests.put<Preferences>(routes.preferences, payload);
|
// TODO: This should probably be a patch request, which isn't offered by the API currently
|
||||||
|
return await this.requests.put<Preferences, UpdatePreferences>(routes.preferences, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createInvitation(payload: CreateInvitation) {
|
async createInvitation(payload: CreateInvitation) {
|
||||||
|
@ -99,6 +100,7 @@ export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMemberPermissions(payload: SetPermissions) {
|
async setMemberPermissions(payload: SetPermissions) {
|
||||||
return await this.requests.put<UserOut>(routes.permissions, payload);
|
// TODO: This should probably be a patch request, which isn't offered by the API currently
|
||||||
|
return await this.requests.put<Permissions, SetPermissions>(routes.permissions, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ interface BasePayload {
|
||||||
|
|
||||||
type exportType = "json";
|
type exportType = "json";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
interface RecipeBulkDelete extends BasePayload {}
|
interface RecipeBulkDelete extends BasePayload {}
|
||||||
|
|
||||||
interface RecipeBulkExport extends BasePayload {
|
interface RecipeBulkExport extends BasePayload {
|
||||||
|
|
|
@ -80,30 +80,13 @@ export class RecipeAPI extends BaseCRUDAPI<Recipe, CreateRecipe> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOneByUrl(url: string) {
|
async createOneByUrl(url: string) {
|
||||||
return await this.requests.post(routes.recipesCreateUrl, { url });
|
return await this.requests.post<string>(routes.recipesCreateUrl, { url });
|
||||||
}
|
}
|
||||||
|
|
||||||
async createManyByUrl(payload: BulkCreatePayload) {
|
async createManyByUrl(payload: BulkCreatePayload) {
|
||||||
return await this.requests.post(routes.recipesCreateUrlBulk, payload);
|
return await this.requests.post(routes.recipesCreateUrlBulk, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods to Generate reference urls for assets/images *
|
|
||||||
recipeImage(recipeSlug: string, version = null, key = null) {
|
|
||||||
return `/api/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeSmallImage(recipeSlug: string, version = null, key = null) {
|
|
||||||
return `/api/media/recipes/${recipeSlug}/images/min-original.webp?&rnd=${key}&version=${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeTinyImage(recipeSlug: string, version = null, key = null) {
|
|
||||||
return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp?&rnd=${key}&version=${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeAssetPath(recipeSlug: string, assetName: string) {
|
|
||||||
return `/api/media/recipes/${recipeSlug}/assets/${assetName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseIngredients(parser: Parser, ingredients: Array<string>) {
|
async parseIngredients(parser: Parser, ingredients: Array<string>) {
|
||||||
parser = parser || "nlp";
|
parser = parser || "nlp";
|
||||||
return await this.requests.post<ParsedIngredient[]>(routes.recipesParseIngredients, { parser, ingredients });
|
return await this.requests.post<ParsedIngredient[]>(routes.recipesParseIngredients, { parser, ingredients });
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Category } from "../categories";
|
import { Category } from "../categories";
|
||||||
import { Tag } from "../tags";
|
import { Tag } from "../tags";
|
||||||
|
import { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
export type Parser = "nlp" | "brute";
|
export type Parser = "nlp" | "brute";
|
||||||
|
|
||||||
|
@ -12,26 +13,14 @@ export interface Confidence {
|
||||||
food?: number;
|
food?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Unit {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
fraction: boolean;
|
|
||||||
abbreviation: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Food {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Ingredient {
|
export interface Ingredient {
|
||||||
referenceId: string;
|
title?: string;
|
||||||
title: string;
|
note?: string;
|
||||||
note: string;
|
unit?: IngredientUnit | CreateIngredientUnit;
|
||||||
unit: Unit | null;
|
food?: IngredientFood | CreateIngredientFood;
|
||||||
food: Food | null;
|
disableAmount?: boolean;
|
||||||
disableAmount: boolean;
|
quantity?: number;
|
||||||
quantity: number;
|
referenceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParsedIngredient {
|
export interface ParsedIngredient {
|
||||||
|
|
|
@ -2,6 +2,6 @@ import { BaseAPI } from "../_base";
|
||||||
|
|
||||||
export class UploadFile extends BaseAPI {
|
export class UploadFile extends BaseAPI {
|
||||||
file(url: string, fileObject: any) {
|
file(url: string, fileObject: any) {
|
||||||
return this.requests.post(url, fileObject);
|
return this.requests.post<string>(url, fileObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ export class UtilsAPI extends BaseAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const token: String = response.data.fileToken;
|
const token: string = response.data.fileToken;
|
||||||
|
|
||||||
const tokenURL = prefix + "/utils/download?token=" + token;
|
const tokenURL = prefix + "/utils/download?token=" + token;
|
||||||
window.open(tokenURL, "_blank");
|
window.open(tokenURL, "_blank");
|
||||||
return await response;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
import axios, { AxiosResponse } from "axios";
|
|
||||||
|
|
||||||
interface RequestResponse<T> {
|
|
||||||
response: AxiosResponse<T> | null;
|
|
||||||
data: T | null;
|
|
||||||
error: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
async safe<T>(funcCall: any, url: string, data: object = {}): Promise<RequestResponse<T>> {
|
|
||||||
const response = await funcCall(url, data).catch(function (error: object) {
|
|
||||||
console.log(error);
|
|
||||||
// Insert Generic Error Handling Here
|
|
||||||
return { response: null, error, data: null };
|
|
||||||
});
|
|
||||||
return { response, error: null, data: response.data };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const requests = {
|
|
||||||
async get<T>(url: string, params = {}): Promise<RequestResponse<T>> {
|
|
||||||
let error = null;
|
|
||||||
const response = await axios.get<T>(url, { ...params }).catch((e) => {
|
|
||||||
error = e;
|
|
||||||
});
|
|
||||||
if (response != null) {
|
|
||||||
return { response, error, data: response?.data };
|
|
||||||
}
|
|
||||||
return { response: null, error, data: null };
|
|
||||||
},
|
|
||||||
|
|
||||||
async post<T>(url: string, data: object) {
|
|
||||||
return await request.safe<T>(axios.post, url, data);
|
|
||||||
},
|
|
||||||
|
|
||||||
async put<T>(url: string, data: object) {
|
|
||||||
return await request.safe<T>(axios.put, url, data);
|
|
||||||
},
|
|
||||||
|
|
||||||
async patch<T>(url: string, data: object) {
|
|
||||||
return await request.safe<T>(axios.patch, url, data);
|
|
||||||
},
|
|
||||||
|
|
||||||
async delete<T>(url: string) {
|
|
||||||
return await request.safe<T>(axios.delete, url);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -22,50 +22,58 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { defineComponent, onMounted, useContext } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
const UPDATE_EVENT = "input";
|
const UPDATE_EVENT = "input";
|
||||||
export default {
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
importBackup: {
|
importBackup: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
setup(_, context) {
|
||||||
return {
|
const { i18n } = useContext();
|
||||||
options: {
|
|
||||||
recipes: {
|
const options = {
|
||||||
value: true,
|
recipes: {
|
||||||
text: this.$t("general.recipes"),
|
value: true,
|
||||||
},
|
text: i18n.t("general.recipes"),
|
||||||
users: {
|
|
||||||
value: true,
|
|
||||||
text: this.$t("user.users"),
|
|
||||||
},
|
|
||||||
groups: {
|
|
||||||
value: true,
|
|
||||||
text: this.$t("group.groups"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
forceImport: false,
|
users: {
|
||||||
};
|
value: true,
|
||||||
},
|
text: i18n.t("user.users"),
|
||||||
mounted() {
|
},
|
||||||
this.emitValue();
|
groups: {
|
||||||
},
|
value: true,
|
||||||
methods: {
|
text: i18n.t("group.groups"),
|
||||||
emitValue() {
|
},
|
||||||
this.$emit(UPDATE_EVENT, {
|
}
|
||||||
recipes: this.options.recipes.value,
|
const forceImport = false;
|
||||||
|
|
||||||
|
function emitValue() {
|
||||||
|
context.emit(UPDATE_EVENT, {
|
||||||
|
recipes: options.recipes.value,
|
||||||
settings: false,
|
settings: false,
|
||||||
themes: false,
|
themes: false,
|
||||||
pages: false,
|
pages: false,
|
||||||
users: this.options.users.value,
|
users: options.users.value,
|
||||||
groups: this.options.groups.value,
|
groups: options.groups.value,
|
||||||
notifications: false,
|
notifications: false,
|
||||||
forceImport: this.forceImport,
|
forceImport,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
emitValue();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
options,
|
||||||
|
forceImport,
|
||||||
|
emitValue,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -92,7 +92,8 @@
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import {defineComponent, ref, useContext} from "@nuxtjs/composition-api";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ const DELETE_EVENT = "delete";
|
||||||
const CLOSE_EVENT = "close";
|
const CLOSE_EVENT = "close";
|
||||||
const JSON_EVENT = "json";
|
const JSON_EVENT = "json";
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
components: { RecipeContextMenu, RecipeFavoriteBadge },
|
components: { RecipeContextMenu, RecipeFavoriteBadge },
|
||||||
props: {
|
props: {
|
||||||
slug: {
|
slug: {
|
||||||
|
@ -129,69 +130,70 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
setup(_, context) {
|
||||||
return {
|
const deleteDialog = ref(false);
|
||||||
deleteDialog: false,
|
|
||||||
edit: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
const { i18n, $globals } = useContext();
|
||||||
editorButtons() {
|
const editorButtons = [
|
||||||
return [
|
{
|
||||||
{
|
text: i18n.t("general.delete"),
|
||||||
text: this.$t("general.delete"),
|
icon: $globals.icons.delete,
|
||||||
icon: this.$globals.icons.delete,
|
event: DELETE_EVENT,
|
||||||
event: DELETE_EVENT,
|
color: "error",
|
||||||
color: "error",
|
},
|
||||||
},
|
{
|
||||||
{
|
text: i18n.t("general.json"),
|
||||||
text: this.$t("general.json"),
|
icon: $globals.icons.codeBraces,
|
||||||
icon: this.$globals.icons.codeBraces,
|
event: JSON_EVENT,
|
||||||
event: JSON_EVENT,
|
color: "accent",
|
||||||
color: "accent",
|
},
|
||||||
},
|
{
|
||||||
{
|
text: i18n.t("general.close"),
|
||||||
text: this.$t("general.close"),
|
icon: $globals.icons.close,
|
||||||
icon: this.$globals.icons.close,
|
event: CLOSE_EVENT,
|
||||||
event: CLOSE_EVENT,
|
color: "",
|
||||||
color: "",
|
},
|
||||||
},
|
{
|
||||||
{
|
text: i18n.t("general.save"),
|
||||||
text: this.$t("general.save"),
|
icon: $globals.icons.save,
|
||||||
icon: this.$globals.icons.save,
|
event: SAVE_EVENT,
|
||||||
event: SAVE_EVENT,
|
color: "success",
|
||||||
color: "success",
|
},
|
||||||
},
|
];
|
||||||
];
|
|
||||||
},
|
function emitHandler(event: string) {
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
emitHandler(event) {
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case CLOSE_EVENT:
|
case CLOSE_EVENT:
|
||||||
this.$emit(CLOSE_EVENT);
|
context.emit(CLOSE_EVENT);
|
||||||
this.$emit("input", false);
|
context.emit("input", false);
|
||||||
break;
|
break;
|
||||||
case SAVE_EVENT:
|
case SAVE_EVENT:
|
||||||
this.$emit(SAVE_EVENT);
|
context.emit(SAVE_EVENT);
|
||||||
break;
|
break;
|
||||||
case JSON_EVENT:
|
case JSON_EVENT:
|
||||||
this.$emit(JSON_EVENT);
|
context.emit(JSON_EVENT);
|
||||||
break;
|
break;
|
||||||
case DELETE_EVENT:
|
case DELETE_EVENT:
|
||||||
this.deleteDialog = true;
|
deleteDialog.value = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
emitDelete() {
|
|
||||||
this.$emit(DELETE_EVENT);
|
function emitDelete() {
|
||||||
this.$emit("input", false);
|
context.emit(DELETE_EVENT);
|
||||||
},
|
context.emit("input", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleteDialog,
|
||||||
|
editorButtons,
|
||||||
|
emitHandler,
|
||||||
|
emitDelete,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
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";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
|
||||||
const BASE_URL = window.location.origin;
|
const BASE_URL = window.location.origin;
|
||||||
|
@ -107,7 +107,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const { $globals, i18n } = useContext();
|
const { $globals, i18n } = useContext();
|
||||||
|
|
||||||
const iconOptions = [
|
const iconOptions = [
|
||||||
|
@ -142,15 +141,16 @@ export default defineComponent({
|
||||||
return iconOptions.find((item) => item.name === icon) || iconOptions[0];
|
return iconOptions.find((item) => item.name === icon) || iconOptions[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { recipeAssetPath } = useStaticRoutes();
|
||||||
function assetURL(assetName: string) {
|
function assetURL(assetName: string) {
|
||||||
return api.recipes.recipeAssetPath(props.slug, assetName);
|
return recipeAssetPath(props.slug, assetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assetEmbed(name: string) {
|
function assetEmbed(name: string) {
|
||||||
return `<img src="${BASE_URL}${assetURL(name)}" height="100%" width="100%"> </img>`;
|
return `<img src="${BASE_URL}${assetURL(name)}" height="100%" width="100%"> </img>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFileObject(fileObject: any) {
|
function setFileObject(fileObject: File) {
|
||||||
state.fileObject = fileObject;
|
state.fileObject = fileObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,13 +51,15 @@
|
||||||
</v-lazy>
|
</v-lazy>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import RecipeChips from "./RecipeChips";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu";
|
import RecipeChips from "./RecipeChips.vue";
|
||||||
import RecipeCardImage from "./RecipeCardImage";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import RecipeRating from "./RecipeRating";
|
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||||
export default {
|
import RecipeRating from "./RecipeRating.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
components: { RecipeFavoriteBadge, RecipeChips, RecipeContextMenu, RecipeRating, RecipeCardImage },
|
components: { RecipeFavoriteBadge, RecipeChips, RecipeContextMenu, RecipeRating, RecipeCardImage },
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -99,17 +101,17 @@ export default {
|
||||||
default: 200,
|
default: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
setup() {
|
||||||
|
const { $auth } = useContext();
|
||||||
|
const loggedIn = computed(() => {
|
||||||
|
return $auth.loggedIn;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fallBackImage: false,
|
loggedIn,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
});
|
||||||
loggedIn() {
|
|
||||||
return this.$store.getters.getIsLoggedIn;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -17,9 +17,11 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import {computed, defineComponent, ref, watch} from "@nuxtjs/composition-api";
|
||||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||||
export default {
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
tiny: {
|
tiny: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -50,44 +52,42 @@ export default {
|
||||||
default: 200,
|
default: 200,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props) {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|
||||||
const { recipeImage, recipeSmallImage, recipeTinyImage } = useStaticRoutes();
|
const { recipeImage, recipeSmallImage, recipeTinyImage } = useStaticRoutes();
|
||||||
|
|
||||||
return { api, recipeImage, recipeSmallImage, recipeTinyImage };
|
const fallBackImage = ref(false);
|
||||||
},
|
const imageSize = computed(() => {
|
||||||
data() {
|
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 {
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -108,4 +108,4 @@ export default {
|
||||||
margin-left: auto !important;
|
margin-left: auto !important;
|
||||||
margin-right: auto !important;
|
margin-right: auto !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,15 +10,7 @@
|
||||||
<v-list-item three-line>
|
<v-list-item three-line>
|
||||||
<slot name="avatar">
|
<slot name="avatar">
|
||||||
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
||||||
<v-img
|
<RecipeCardImage :icon-size="100" :height="125" :slug="slug" small :image-version="image"></RecipeCardImage>
|
||||||
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>
|
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
</slot>
|
</slot>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
|
@ -61,15 +53,17 @@
|
||||||
</v-expand-transition>
|
</v-expand-transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge.vue";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu";
|
import RecipeContextMenu from "./RecipeContextMenu.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import RecipeCardImage from "./RecipeCardImage.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipeFavoriteBadge,
|
RecipeFavoriteBadge,
|
||||||
RecipeContextMenu,
|
RecipeContextMenu,
|
||||||
|
RecipeCardImage,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -89,8 +83,9 @@ export default defineComponent({
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
type: [String, null],
|
type: String,
|
||||||
default: "",
|
required: false,
|
||||||
|
default: "abc123",
|
||||||
},
|
},
|
||||||
route: {
|
route: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -102,24 +97,14 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const api = useUserApi();
|
const { $auth } = useContext();
|
||||||
|
const loggedIn = computed(() => {
|
||||||
|
return $auth.loggedIn;
|
||||||
|
});
|
||||||
|
|
||||||
return { api };
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
return {
|
||||||
fallBackImage: false,
|
loggedIn,
|
||||||
};
|
}
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
loggedIn() {
|
|
||||||
return this.$store.getters.getIsLoggedIn;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getImage(slug) {
|
|
||||||
return this.api.recipes.recipeSmallImage(slug, this.image);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -102,13 +102,16 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import RecipeCard from "./RecipeCard";
|
import { computed, defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
|
||||||
import RecipeCardMobile from "./RecipeCardMobile";
|
import RecipeCard from "./RecipeCard.vue";
|
||||||
|
import RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import { useSorter } from "~/composables/recipes";
|
import { useSorter } from "~/composables/recipes";
|
||||||
|
import {Recipe} from "~/types/api-types/recipe";
|
||||||
|
|
||||||
const SORT_EVENT = "sort";
|
const SORT_EVENT = "sort";
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipeCard,
|
RecipeCard,
|
||||||
RecipeCardMobile,
|
RecipeCardMobile,
|
||||||
|
@ -126,100 +129,90 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
hardLimit: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: 99999,
|
|
||||||
},
|
|
||||||
mobileCards: {
|
mobileCards: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
singleColumn: {
|
singleColumn: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
defualt: false,
|
default: false,
|
||||||
},
|
},
|
||||||
recipes: {
|
recipes: {
|
||||||
type: Array,
|
type: Array as () => Recipe[],
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, context) {
|
||||||
const utils = useSorter();
|
const utils = useSorter();
|
||||||
|
|
||||||
return { utils };
|
const EVENTS = {
|
||||||
},
|
az: "az",
|
||||||
data() {
|
rating: "rating",
|
||||||
return {
|
created: "created",
|
||||||
sortLoading: false,
|
updated: "updated",
|
||||||
loading: false,
|
shuffle: "shuffle",
|
||||||
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: {
|
const { $globals, $vuetify } = useContext();
|
||||||
async setLoader() {
|
const viewScale = computed(() => {
|
||||||
this.loading = true;
|
return props.mobileCards || $vuetify.breakpoint.smAndDown;
|
||||||
// eslint-disable-next-line promise/param-names
|
});
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
|
||||||
this.loading = false;
|
const displayTitleIcon = computed(() => {
|
||||||
},
|
return props.icon || $globals.icons.tags;
|
||||||
navigateRandom() {
|
});
|
||||||
const recipe = this.recipes[Math.floor(Math.random() * this.recipes.length)];
|
|
||||||
this.$router.push(`/recipe/${recipe.slug}`);
|
const state = reactive({
|
||||||
},
|
sortLoading: false,
|
||||||
sortRecipes(sortType) {
|
})
|
||||||
this.sortLoading = true;
|
|
||||||
const sortTarget = [...this.recipes];
|
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) {
|
switch (sortType) {
|
||||||
case this.EVENTS.az:
|
case EVENTS.az:
|
||||||
this.utils.sortAToZ(sortTarget);
|
utils.sortAToZ(sortTarget);
|
||||||
break;
|
break;
|
||||||
case this.EVENTS.rating:
|
case EVENTS.rating:
|
||||||
this.utils.sortByRating(sortTarget);
|
utils.sortByRating(sortTarget);
|
||||||
break;
|
break;
|
||||||
case this.EVENTS.created:
|
case EVENTS.created:
|
||||||
this.utils.sortByCreated(sortTarget);
|
utils.sortByCreated(sortTarget);
|
||||||
break;
|
break;
|
||||||
case this.EVENTS.updated:
|
case EVENTS.updated:
|
||||||
this.utils.sortByUpdated(sortTarget);
|
utils.sortByUpdated(sortTarget);
|
||||||
break;
|
break;
|
||||||
case this.EVENTS.shuffle:
|
case EVENTS.shuffle:
|
||||||
this.utils.shuffle(sortTarget);
|
utils.shuffle(sortTarget);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log("Unknown Event", sortType);
|
console.log("Unknown Event", sortType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$emit(SORT_EVENT, sortTarget);
|
context.emit(SORT_EVENT, sortTarget);
|
||||||
this.sortLoading = false;
|
state.sortLoading = false;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
EVENTS,
|
||||||
|
viewScale,
|
||||||
|
displayTitleIcon,
|
||||||
|
navigateRandom,
|
||||||
|
sortRecipes,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -40,16 +40,14 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs, watch } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
const CREATED_ITEM_EVENT = "created-item";
|
const CREATED_ITEM_EVENT = "created-item";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
buttonText: {
|
|
||||||
type: String,
|
|
||||||
default: "Add",
|
|
||||||
},
|
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
|
@ -63,55 +61,49 @@ export default defineComponent({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, context) {
|
||||||
const api = useUserApi();
|
const title = computed(() => props.tagDialog ? "Create a Tag" : "Create a Category");
|
||||||
|
const inputLabel = computed(() => props.tagDialog ? "Tag Name" : "Category Name");
|
||||||
|
|
||||||
return { api };
|
const rules = {
|
||||||
},
|
required: (val: string) => !!val || "A Name is Required",
|
||||||
data() {
|
};
|
||||||
return {
|
|
||||||
|
const state = reactive({
|
||||||
dialog: false,
|
dialog: false,
|
||||||
itemName: "",
|
itemName: "",
|
||||||
rules: {
|
});
|
||||||
required: (val) => !!val || "A Name is Required",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
watch(() => state.dialog, (val: boolean) => {
|
||||||
title() {
|
if (!val) state.itemName = "";
|
||||||
return this.tagDialog ? "Create a Tag" : "Create a Category";
|
});
|
||||||
},
|
|
||||||
inputLabel() {
|
|
||||||
return this.tagDialog ? "Tag Name" : "Category Name";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
dialog(val) {
|
|
||||||
if (!val) this.itemName = "";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
const api = useUserApi();
|
||||||
open() {
|
async function select() {
|
||||||
this.dialog = true;
|
|
||||||
},
|
|
||||||
async select() {
|
|
||||||
const newItem = await (async () => {
|
const newItem = await (async () => {
|
||||||
if (this.tagDialog) {
|
if (props.tagDialog) {
|
||||||
const { data } = await this.api.tags.createOne({ name: this.itemName });
|
const { data } = await api.tags.createOne({ name: state.itemName });
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
const { data } = await this.api.categories.createOne({ name: this.itemName });
|
const { data } = await api.categories.createOne({ name: state.itemName });
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
console.log(newItem);
|
console.log(newItem);
|
||||||
|
|
||||||
this.$emit(CREATED_ITEM_EVENT, newItem);
|
context.emit(CREATED_ITEM_EVENT, newItem);
|
||||||
this.dialog = false;
|
state.dialog = false;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
title,
|
||||||
|
inputLabel,
|
||||||
|
rules,
|
||||||
|
select,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -42,18 +42,22 @@
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, onMounted, reactive, toRefs, useContext, watch } from "@nuxtjs/composition-api";
|
||||||
import RecipeCategoryTagDialog from "./RecipeCategoryTagDialog.vue";
|
import RecipeCategoryTagDialog from "./RecipeCategoryTagDialog.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
|
||||||
import { useTags, useCategories } from "~/composables/recipes";
|
import { useTags, useCategories } from "~/composables/recipes";
|
||||||
|
import { Category } from "~/api/class-interfaces/categories";
|
||||||
|
import { Tag } from "~/api/class-interfaces/tags";
|
||||||
|
|
||||||
const MOUNTED_EVENT = "mounted";
|
const MOUNTED_EVENT = "mounted";
|
||||||
export default {
|
|
||||||
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipeCategoryTagDialog,
|
RecipeCategoryTagDialog,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: Array as () => (Category | Tag | string)[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
solo: {
|
solo: {
|
||||||
|
@ -90,74 +94,74 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup(props, context) {
|
||||||
const api = useUserApi();
|
|
||||||
|
|
||||||
const { allTags, useAsyncGetAll: getAllTags } = useTags();
|
const { allTags, useAsyncGetAll: getAllTags } = useTags();
|
||||||
const { allCategories, useAsyncGetAll: getAllCategories } = useCategories();
|
const { allCategories, useAsyncGetAll: getAllCategories } = useCategories();
|
||||||
getAllCategories();
|
getAllCategories();
|
||||||
getAllTags();
|
getAllTags();
|
||||||
|
|
||||||
return { api, allTags, allCategories, getAllCategories, getAllTags };
|
const state = reactive({
|
||||||
},
|
selected: props.value,
|
||||||
|
});
|
||||||
|
watch(() => props.value, (val) => {
|
||||||
|
state.selected = val;
|
||||||
|
});
|
||||||
|
|
||||||
data() {
|
const { i18n } = useContext();
|
||||||
return {
|
const inputLabel = computed(() => {
|
||||||
selected: [],
|
if (!props.showLabel) return null;
|
||||||
};
|
return props.tagSelector ? i18n.t("tag.tags") : i18n.t("recipe.categories");
|
||||||
},
|
});
|
||||||
|
|
||||||
computed: {
|
const activeItems = computed(() => {
|
||||||
inputLabel() {
|
let itemObjects: Tag[] | Category[] | null;
|
||||||
if (!this.showLabel) return null;
|
if (props.tagSelector) itemObjects = allTags.value;
|
||||||
return this.tagSelector ? this.$t("tag.tags") : this.$t("recipe.categories");
|
|
||||||
},
|
|
||||||
activeItems() {
|
|
||||||
let ItemObjects = [];
|
|
||||||
if (this.tagSelector) ItemObjects = this.allTags;
|
|
||||||
else {
|
else {
|
||||||
ItemObjects = this.allCategories;
|
itemObjects = allCategories.value;
|
||||||
}
|
}
|
||||||
if (this.returnObject) return ItemObjects;
|
if (props.returnObject) return itemObjects;
|
||||||
else {
|
else {
|
||||||
return ItemObjects.map((x) => x.name);
|
return itemObjects?.map((x: Tag | Category) => x.name);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
flat() {
|
|
||||||
if (this.selected) {
|
const flat = computed(() => {
|
||||||
return this.selected.length > 0 && this.solo;
|
if (state.selected) {
|
||||||
|
return state.selected.length > 0 && props.solo;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
});
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
function emitChange() {
|
||||||
value(val) {
|
context.emit("input", state.selected);
|
||||||
this.selected = val;
|
}
|
||||||
},
|
|
||||||
},
|
// TODO Is this needed?
|
||||||
mounted() {
|
onMounted(() => {
|
||||||
this.$emit(MOUNTED_EVENT);
|
context.emit(MOUNTED_EVENT);
|
||||||
this.setInit(this.value);
|
});
|
||||||
},
|
|
||||||
methods: {
|
function removeByIndex(index: number) {
|
||||||
emitChange() {
|
state.selected.splice(index, 1);
|
||||||
this.$emit("input", this.selected);
|
}
|
||||||
},
|
|
||||||
setInit(val) {
|
function pushToItem(createdItem: Tag | Category) {
|
||||||
this.selected = val;
|
|
||||||
},
|
|
||||||
removeByIndex(index) {
|
|
||||||
this.selected.splice(index, 1);
|
|
||||||
},
|
|
||||||
pushToItem(createdItem) {
|
|
||||||
createdItem = this.returnObject ? createdItem : createdItem.name;
|
|
||||||
// TODO: Remove excessive get calls
|
// TODO: Remove excessive get calls
|
||||||
this.getAllCategories();
|
getAllCategories();
|
||||||
this.getAllTags();
|
getAllTags();
|
||||||
this.selected.push(createdItem);
|
state.selected.push(createdItem);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
inputLabel,
|
||||||
|
activeItems,
|
||||||
|
flat,
|
||||||
|
emitChange,
|
||||||
|
removeByIndex,
|
||||||
|
pushToItem,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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";
|
type ItemType = "tags" | "categories" | "tools";
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// @ts-ignore
|
|
||||||
const { i18n, $globals } = useContext();
|
const { i18n, $globals } = useContext();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
@ -77,8 +76,12 @@ export default defineComponent({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useMeta(() => ({
|
||||||
|
title: state.headline,
|
||||||
|
}));
|
||||||
|
|
||||||
const itemsSorted = computed(() => {
|
const itemsSorted = computed(() => {
|
||||||
const byLetter: { [key: string]: Array<any> } = {};
|
const byLetter: { [key: string]: Array<GenericItem> } = {};
|
||||||
|
|
||||||
if (!props.items) return byLetter;
|
if (!props.items) return byLetter;
|
||||||
|
|
||||||
|
@ -99,10 +102,7 @@ export default defineComponent({
|
||||||
itemsSorted,
|
itemsSorted,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
head() {
|
// Needed for useMeta
|
||||||
return {
|
head: {},
|
||||||
title: this.headline as string,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import {computed, defineComponent} from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
truncate: {
|
truncate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -48,39 +50,23 @@ export default {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
setup(props) {
|
||||||
allCategories() {
|
const urlParam = computed(() => props.isCategory ? "categories" : "tags");
|
||||||
return this.$store.getters.getAllCategories || [];
|
|
||||||
},
|
|
||||||
allTags() {
|
|
||||||
return this.$store.getters.getAllTags || [];
|
|
||||||
},
|
|
||||||
urlParam() {
|
|
||||||
return this.isCategory ? "categories" : "tags";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getSlug(name) {
|
|
||||||
if (!name) return;
|
|
||||||
|
|
||||||
if (this.isCategory) {
|
function truncateText(text: string, length = 20, clamp = "...") {
|
||||||
const matches = this.allCategories.filter((x) => x.name === name);
|
if (!props.truncate) return text;
|
||||||
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 || "...";
|
|
||||||
const node = document.createElement("div");
|
const node = document.createElement("div");
|
||||||
node.innerHTML = text;
|
node.innerHTML = text;
|
||||||
const content = node.textContent;
|
const content = node.textContent || "";
|
||||||
return content.length > length ? content.slice(0, length) + clamp : content;
|
return content.length > length ? content.slice(0, length) + clamp : content;
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
urlParam,
|
||||||
|
truncateText,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
@ -167,7 +167,6 @@ export default defineComponent({
|
||||||
pickerMenu: false,
|
pickerMenu: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const { i18n, $globals } = useContext();
|
const { i18n, $globals } = useContext();
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
@ -262,14 +261,12 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Print is handled as an event in the parent component
|
// Note: Print is handled as an event in the parent component
|
||||||
const eventHandlers: { [key: string]: Function } = {
|
const eventHandlers: { [key: string]: () => void } = {
|
||||||
// @ts-ignore - Doens't know about open()
|
|
||||||
delete: () => {
|
delete: () => {
|
||||||
state.recipeDeleteDialog = true;
|
state.recipeDeleteDialog = true;
|
||||||
},
|
},
|
||||||
edit: () => router.push(`/recipe/${props.slug}` + "?edit=true"),
|
edit: () => router.push(`/recipe/${props.slug}` + "?edit=true"),
|
||||||
download: handleDownloadEvent,
|
download: handleDownloadEvent,
|
||||||
// @ts-ignore - Doens't know about open()
|
|
||||||
mealplanner: () => {
|
mealplanner: () => {
|
||||||
state.mealplannerDialog = true;
|
state.mealplannerDialog = true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
||||||
import RecipeChip from "./RecipeChips.vue";
|
import RecipeChip from "./RecipeChips.vue";
|
||||||
|
@ -49,13 +49,13 @@ import { UserOut } from "~/types/api-types/user";
|
||||||
const INPUT_EVENT = "input";
|
const INPUT_EVENT = "input";
|
||||||
|
|
||||||
interface ShowHeaders {
|
interface ShowHeaders {
|
||||||
id: Boolean;
|
id: boolean;
|
||||||
owner: Boolean;
|
owner: boolean;
|
||||||
tags: Boolean;
|
tags: boolean;
|
||||||
categories: Boolean;
|
categories: boolean;
|
||||||
tools: Boolean;
|
tools: boolean;
|
||||||
recipeYield: Boolean;
|
recipeYield: boolean;
|
||||||
dateAdded: Boolean;
|
dateAdded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -129,7 +129,7 @@ export default defineComponent({
|
||||||
// ============
|
// ============
|
||||||
// Group Members
|
// Group Members
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const members = ref<UserOut[] | null[]>([]);
|
const members = ref<UserOut[]>([]);
|
||||||
|
|
||||||
async function refreshMembers() {
|
async function refreshMembers() {
|
||||||
const { data } = await api.groups.fetchMembers();
|
const { data } = await api.groups.fetchMembers();
|
||||||
|
@ -142,10 +142,9 @@ export default defineComponent({
|
||||||
refreshMembers();
|
refreshMembers();
|
||||||
});
|
});
|
||||||
|
|
||||||
function getMember(id: number) {
|
function getMember(id: string) {
|
||||||
if (members.value[0]) {
|
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";
|
return "None";
|
||||||
|
@ -165,4 +164,4 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 RecipeCardMobile from "./RecipeCardMobile.vue";
|
||||||
import { useRecipes, allRecipes, useRecipeSearch } from "~/composables/recipes";
|
import { useRecipes, allRecipes, useRecipeSearch } from "~/composables/recipes";
|
||||||
import { RecipeSummary } from "~/types/api-types/recipe";
|
import { RecipeSummary } from "~/types/api-types/recipe";
|
||||||
|
@ -74,7 +74,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// Dialong State Management
|
// Dialog State Management
|
||||||
const dialog = ref(false);
|
const dialog = ref(false);
|
||||||
|
|
||||||
// Reset or Grab Recipes on Change
|
// 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() {
|
function open() {
|
||||||
dialog.value = true;
|
dialog.value = true;
|
||||||
}
|
}
|
||||||
|
@ -110,56 +157,6 @@ export default defineComponent({
|
||||||
|
|
||||||
return { allRecipes, refreshRecipes, ...toRefs(state), dialog, open, close, handleSelect, search, results };
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -167,4 +164,4 @@ export default defineComponent({
|
||||||
.scroll {
|
.scroll {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -20,9 +20,10 @@
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import {UserOut} from "~/types/api-types/user";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
slug: {
|
slug: {
|
||||||
|
@ -38,31 +39,29 @@ export default defineComponent({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props) {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
const { $auth } = useContext();
|
||||||
|
|
||||||
return { api };
|
// TODO Setup the correct type for $auth.user
|
||||||
},
|
// See https://github.com/nuxt-community/auth-module/issues/1097
|
||||||
computed: {
|
const user = computed(() => $auth.user as unknown as UserOut);
|
||||||
user() {
|
const isFavorite = computed(() => user.value?.favoriteRecipes?.includes(props.slug));
|
||||||
return this.$auth.user;
|
|
||||||
},
|
async function toggleFavorite() {
|
||||||
isFavorite() {
|
console.log("Favorited?");
|
||||||
return this.$auth.user.favoriteRecipes.includes(this.slug);
|
if (!isFavorite.value) {
|
||||||
},
|
await api.users.addFavorite(user.value?.id, props.slug);
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async toggleFavorite() {
|
|
||||||
if (!this.isFavorite) {
|
|
||||||
await this.api.users.addFavorite(this.$auth.user.id, this.slug);
|
|
||||||
} else {
|
} 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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text class="mt-n5">
|
<v-card-text class="mt-n5">
|
||||||
<div>
|
<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>
|
<template #append-outer>
|
||||||
<v-btn class="ml-2" color="primary" :loading="loading" :disabled="!slug" @click="getImageFromURL">
|
<v-btn class="ml-2" color="primary" :loading="loading" :disabled="!slug" @click="getImageFromURL">
|
||||||
{{ $t("general.get") }}
|
{{ $t("general.get") }}
|
||||||
|
@ -39,11 +39,13 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
const REFRESH_EVENT = "refresh";
|
const REFRESH_EVENT = "refresh";
|
||||||
const UPLOAD_EVENT = "upload";
|
const UPLOAD_EVENT = "upload";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
slug: {
|
slug: {
|
||||||
|
@ -51,32 +53,37 @@ export default defineComponent({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, context) {
|
||||||
const api = useUserApi();
|
const state = reactive({
|
||||||
|
url: "",
|
||||||
|
loading: false,
|
||||||
|
menu: false,
|
||||||
|
})
|
||||||
|
|
||||||
return { api };
|
function uploadImage(fileObject: File) {
|
||||||
},
|
context.emit(UPLOAD_EVENT, fileObject);
|
||||||
data: () => ({
|
state.menu = false;
|
||||||
url: "",
|
}
|
||||||
loading: false,
|
|
||||||
menu: false,
|
const api = useUserApi();
|
||||||
}),
|
async function getImageFromURL() {
|
||||||
methods: {
|
state.loading = true;
|
||||||
uploadImage(fileObject) {
|
if (await api.recipes.updateImagebyURL(props.slug, state.url)) {
|
||||||
this.$emit(UPLOAD_EVENT, fileObject);
|
context.emit(REFRESH_EVENT);
|
||||||
this.menu = false;
|
|
||||||
},
|
|
||||||
async getImageFromURL() {
|
|
||||||
this.loading = true;
|
|
||||||
if (await this.api.recipes.updateImagebyURL(this.slug, this.url)) {
|
|
||||||
this.$emit(REFRESH_EVENT);
|
|
||||||
}
|
}
|
||||||
this.loading = false;
|
state.loading = false;
|
||||||
this.menu = false;
|
state.menu = false;
|
||||||
},
|
}
|
||||||
getMessages() {
|
|
||||||
return this.slug ? [""] : [this.$i18n.t("recipe.save-recipe-before-use")];
|
const { i18n } = useContext();
|
||||||
},
|
const messages = props.slug ? [""] : [i18n.t("recipe.save-recipe-before-use")];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
uploadImage,
|
||||||
|
getImageFromURL,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -105,11 +105,12 @@
|
||||||
import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api";
|
||||||
import { useFoods, useUnits } from "~/composables/recipes";
|
import { useFoods, useUnits } from "~/composables/recipes";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object as () => RecipeIngredient,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
disableAmount: {
|
disableAmount: {
|
||||||
|
@ -157,14 +158,14 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUnitEnter() {
|
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");
|
console.log("Creating");
|
||||||
createAssignUnit();
|
createAssignUnit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFoodEnter() {
|
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");
|
console.log("Creating");
|
||||||
createAssignFood();
|
createAssignFood();
|
||||||
}
|
}
|
||||||
|
@ -194,4 +195,4 @@ export default defineComponent({
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -23,17 +23,20 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
|
||||||
|
// @ts-ignore
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
import { parseIngredientText } from "~/composables/recipes";
|
import { parseIngredientText } from "~/composables/recipes";
|
||||||
|
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: Array as () => RecipeIngredient[],
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
disableAmount: {
|
disableAmount: {
|
||||||
|
@ -46,6 +49,15 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
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(() => {
|
const ingredientCopyText = computed(() => {
|
||||||
return props.value
|
return props.value
|
||||||
.map((ingredient) => {
|
.map((ingredient) => {
|
||||||
|
@ -54,41 +66,18 @@ export default defineComponent({
|
||||||
.join("\n");
|
.join("\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
return { parseIngredientText, ingredientCopyText };
|
function toggleChecked(index: number) {
|
||||||
},
|
// TODO Find a better way to do this - $set is not available, and
|
||||||
data() {
|
// direct array modifications are not propagated for some reason
|
||||||
return {
|
state.checked.splice(index, 1, !state.checked[index]);
|
||||||
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]);
|
|
||||||
},
|
|
||||||
|
|
||||||
validateTitle(title) {
|
return {
|
||||||
return !(title === null || title === "");
|
...toRefs(state),
|
||||||
},
|
parseIngredientText,
|
||||||
toggleShowTitle(index) {
|
ingredientCopyText,
|
||||||
const newVal = !this.showTitleEditor[index];
|
toggleChecked,
|
||||||
if (!newVal) {
|
};
|
||||||
this.value[index].title = "";
|
|
||||||
}
|
|
||||||
this.$set(this.showTitleEditor, index, newVal);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -153,7 +153,7 @@ import draggable from "vuedraggable";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
import { ref, toRefs, reactive, defineComponent, watch, onMounted } from "@nuxtjs/composition-api";
|
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 { parseIngredientText } from "~/composables/recipes";
|
||||||
import { uuid4 } from "~/composables/use-utils";
|
import { uuid4 } from "~/composables/use-utils";
|
||||||
|
|
||||||
|
@ -227,14 +227,18 @@ export default defineComponent({
|
||||||
state.disabledSteps = [];
|
state.disabledSteps = [];
|
||||||
|
|
||||||
v.forEach((element) => {
|
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?
|
// Eliminate state with an eager call to watcher?
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
props.value.forEach((element) => {
|
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
|
// Ingredient Linker
|
||||||
const activeRefs = ref<String[]>([]);
|
const activeRefs = ref<string[]>([]);
|
||||||
const activeIndex = ref(0);
|
const activeIndex = ref(0);
|
||||||
const activeText = ref("");
|
const activeText = ref("");
|
||||||
|
|
||||||
function openDialog(idx: number, refs: IngredientToStepRef[], text: string) {
|
function openDialog(idx: number, refs: IngredientReferences[], text: string) {
|
||||||
setUsedIngredients();
|
setUsedIngredients();
|
||||||
activeText.value = text;
|
activeText.value = text;
|
||||||
activeIndex.value = idx;
|
activeIndex.value = idx;
|
||||||
state.dialog = true;
|
state.dialog = true;
|
||||||
activeRefs.value = refs.map((ref) => ref.referenceId);
|
activeRefs.value = refs.map((ref) => ref.referenceId ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function setIngredientIds() {
|
function setIngredientIds() {
|
||||||
const instruction = props.value[activeIndex.value];
|
const instruction = props.value[activeIndex.value];
|
||||||
instruction.ingredientReferences = activeRefs.value.map((ref) => {
|
instruction.ingredientReferences = activeRefs.value.map((ref) => {
|
||||||
return {
|
return {
|
||||||
referenceId: ref as string,
|
referenceId: ref,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
state.dialog = false;
|
state.dialog = false;
|
||||||
|
@ -294,17 +298,19 @@ export default defineComponent({
|
||||||
const usedRefs: { [key: string]: boolean } = {};
|
const usedRefs: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
props.value.forEach((element) => {
|
props.value.forEach((element) => {
|
||||||
element.ingredientReferences.forEach((ref) => {
|
element.ingredientReferences?.forEach((ref) => {
|
||||||
usedRefs[ref.referenceId] = true;
|
if (ref.referenceId !== undefined) {
|
||||||
|
usedRefs[ref.referenceId] = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
state.usedIngredients = props.ingredients.filter((ing) => {
|
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) => {
|
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) => {
|
props.ingredients.forEach((ingredient) => {
|
||||||
const searchText = parseIngredientText(ingredient, props.disableAmount);
|
const searchText = parseIngredientText(ingredient, props.disableAmount);
|
||||||
|
|
||||||
|
if (ingredient.referenceId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (searchText.toLowerCase().includes(" " + word) && !activeRefs.value.includes(ingredient.referenceId)) {
|
if (searchText.toLowerCase().includes(" " + word) && !activeRefs.value.includes(ingredient.referenceId)) {
|
||||||
console.info("Word Matched", `'${word}'`, ingredient.note);
|
console.info("Word Matched", `'${word}'`, ingredient.note);
|
||||||
activeRefs.value.push(ingredient.referenceId);
|
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) || "";
|
const ing = props.ingredients.find((ing) => ing.referenceId === refId) || "";
|
||||||
if (ing === "") {
|
if (ing === "") {
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -31,9 +31,12 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
// @ts-ignore
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
export default {
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
},
|
},
|
||||||
|
@ -48,15 +51,21 @@ export default {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
setup(props) {
|
||||||
addNote() {
|
function addNote() {
|
||||||
this.value.push({ title: "", text: "" });
|
props.value.push({ title: "", text: "" });
|
||||||
},
|
}
|
||||||
removeByIndex(list, index) {
|
|
||||||
|
function removeByIndex(list: unknown[], index: number) {
|
||||||
list.splice(index, 1);
|
list.splice(index, 1);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addNote,
|
||||||
|
removeByIndex,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
@ -33,11 +33,14 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
|
import { Nutrition } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object as () => Nutrition,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
|
@ -45,59 +48,59 @@ export default {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
setup(props, context) {
|
||||||
return {
|
const { i18n } = useContext();
|
||||||
labels: {
|
const labels = {
|
||||||
calories: {
|
calories: {
|
||||||
label: this.$t("recipe.calories"),
|
label: i18n.t("recipe.calories"),
|
||||||
suffix: this.$t("recipe.calories-suffix"),
|
suffix: i18n.t("recipe.calories-suffix"),
|
||||||
},
|
},
|
||||||
fatContent: {
|
fatContent: {
|
||||||
label: this.$t("recipe.fat-content"),
|
label: i18n.t("recipe.fat-content"),
|
||||||
suffix: this.$t("recipe.grams"),
|
suffix: i18n.t("recipe.grams"),
|
||||||
},
|
},
|
||||||
fiberContent: {
|
fiberContent: {
|
||||||
label: this.$t("recipe.fiber-content"),
|
label: i18n.t("recipe.fiber-content"),
|
||||||
suffix: this.$t("recipe.grams"),
|
suffix: i18n.t("recipe.grams"),
|
||||||
},
|
},
|
||||||
proteinContent: {
|
proteinContent: {
|
||||||
label: this.$t("recipe.protein-content"),
|
label: i18n.t("recipe.protein-content"),
|
||||||
suffix: this.$t("recipe.grams"),
|
suffix: i18n.t("recipe.grams"),
|
||||||
},
|
},
|
||||||
sodiumContent: {
|
sodiumContent: {
|
||||||
label: this.$t("recipe.sodium-content"),
|
label: i18n.t("recipe.sodium-content"),
|
||||||
suffix: this.$t("recipe.milligrams"),
|
suffix: i18n.t("recipe.milligrams"),
|
||||||
},
|
},
|
||||||
sugarContent: {
|
sugarContent: {
|
||||||
label: this.$t("recipe.sugar-content"),
|
label: i18n.t("recipe.sugar-content"),
|
||||||
suffix: this.$t("recipe.grams"),
|
suffix: i18n.t("recipe.grams"),
|
||||||
},
|
},
|
||||||
carbohydrateContent: {
|
carbohydrateContent: {
|
||||||
label: this.$t("recipe.carbohydrate-content"),
|
label: i18n.t("recipe.carbohydrate-content"),
|
||||||
suffix: this.$t("recipe.grams"),
|
suffix: i18n.t("recipe.grams"),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
const valueNotNull = computed(() => {
|
||||||
computed: {
|
Object.values(props.value).forEach((valueProperty) => {
|
||||||
showViewer() {
|
|
||||||
return !this.edit && this.valueNotNull;
|
|
||||||
},
|
|
||||||
valueNotNull() {
|
|
||||||
for (const property in this.value) {
|
|
||||||
const valueProperty = this.value[property];
|
|
||||||
if (valueProperty && valueProperty !== "") return true;
|
if (valueProperty && valueProperty !== "") return true;
|
||||||
}
|
});
|
||||||
return false;
|
return false;
|
||||||
},
|
});
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
const showViewer = computed(() => !props.edit && valueNotNull.value);
|
||||||
updateValue(key, value) {
|
|
||||||
this.$emit("input", { ...this.value, [key]: value });
|
function updateValue(key: number | string, event: Event) {
|
||||||
},
|
context.emit("input", { ...props.value, [key]: event });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
valueNotNull,
|
||||||
|
showViewer,
|
||||||
|
updateValue
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -25,6 +25,7 @@ export default defineComponent({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
// TODO Remove name prop?
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
|
@ -42,36 +43,26 @@ export default defineComponent({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, context) {
|
||||||
const api = useUserApi();
|
const { $auth } = useContext();
|
||||||
|
const loggedIn = computed(() => {
|
||||||
|
return $auth.loggedIn;
|
||||||
|
});
|
||||||
|
|
||||||
return { api };
|
const rating = ref(props.value);
|
||||||
},
|
|
||||||
data() {
|
const api = useUserApi();
|
||||||
return {
|
function updateRating(val: number) {
|
||||||
rating: 0,
|
if (props.emitOnly) {
|
||||||
};
|
context.emit("input", val);
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
loggedIn() {
|
|
||||||
return this.$auth.loggedIn;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.rating = this.value;
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateRating(val) {
|
|
||||||
if (this.emitOnly) {
|
|
||||||
this.$emit("input", val);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.api.recipes.patchOne(this.slug, {
|
api.recipes.patchOne(props.slug, {
|
||||||
name: this.name,
|
|
||||||
slug: this.slug,
|
|
||||||
rating: val,
|
rating: val,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return { loggedIn, rating, updateRating };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -34,9 +34,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
components: {},
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -47,23 +48,23 @@ export default {
|
||||||
required: false,
|
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: {
|
return {
|
||||||
labels() {
|
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",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
});
|
||||||
methods: {},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
prepTime: {
|
prepTime: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -26,29 +28,39 @@ export default {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
setup(props) {
|
||||||
showCards() {
|
const { i18n } = useContext();
|
||||||
return [this.prepTime, this.totalTime, this.performTime].some((x) => !this.isEmpty(x));
|
|
||||||
},
|
function isEmpty(str: string | null) {
|
||||||
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) {
|
|
||||||
return !str || str.length === 0;
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, toRefs, reactive, useContext, computed } from "@nuxtjs/composition-api";
|
import { defineComponent, toRefs, reactive, useContext, computed } from "@nuxtjs/composition-api";
|
||||||
|
import { UserOut } from "~/types/api-types/user";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
@ -33,7 +34,9 @@ export default defineComponent({
|
||||||
const { $auth } = useContext();
|
const { $auth } = useContext();
|
||||||
|
|
||||||
const imageURL = computed(() => {
|
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}`;
|
return `/api/media/users/${props.userId}/profile.webp?cacheKey=${key}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -43,4 +46,4 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -46,10 +46,11 @@
|
||||||
</template>
|
</template>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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";
|
import RecipeDialogSearch from "~/components/Domain/Recipe/RecipeDialogSearch.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeDialogSearch },
|
components: { RecipeDialogSearch },
|
||||||
props: {
|
props: {
|
||||||
|
@ -59,36 +60,32 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const domSearchDialog = ref(null);
|
const domSearchDialog = ref<InstanceType<typeof RecipeDialogSearch> | null>(null);
|
||||||
|
|
||||||
function activateSearch() {
|
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 {
|
return {
|
||||||
activateSearch,
|
activateSearch,
|
||||||
domSearchDialog,
|
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>
|
</script>
|
||||||
|
|
|
@ -32,16 +32,14 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||||
import { toastAlert, toastLoading } from "~/composables/use-toast";
|
import { toastAlert, toastLoading } from "~/composables/use-toast";
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
return { toastAlert, toastLoading };
|
const icon = computed(() => {
|
||||||
},
|
switch (toastAlert.color) {
|
||||||
computed: {
|
|
||||||
icon() {
|
|
||||||
switch (this.toastAlert.color) {
|
|
||||||
case "error":
|
case "error":
|
||||||
return "mdi-alert";
|
return "mdi-alert";
|
||||||
case "success":
|
case "success":
|
||||||
|
@ -51,7 +49,9 @@ export default {
|
||||||
default:
|
default:
|
||||||
return "mdi-alert";
|
return "mdi-alert";
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
|
|
||||||
|
return { icon, toastAlert, toastLoading };
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -34,8 +34,11 @@
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { VTooltip } from "~/types/vuetify";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
copyText: {
|
copyText: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -54,30 +57,34 @@ export default {
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
setup(props) {
|
||||||
return {
|
const show = ref(false);
|
||||||
show: false,
|
const copyToolTip = ref<VTooltip | null>(null);
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
function toggleBlur() {
|
||||||
toggleBlur() {
|
copyToolTip.value?.deactivate();
|
||||||
this.$refs.copyToolTip.deactivate();
|
}
|
||||||
},
|
|
||||||
textToClipboard() {
|
function textToClipboard() {
|
||||||
this.show = true;
|
show.value = true;
|
||||||
const copyText = this.copyText;
|
const copyText = props.copyText;
|
||||||
navigator.clipboard.writeText(copyText).then(
|
navigator.clipboard.writeText(copyText).then(
|
||||||
() => console.log(`Copied\n${copyText}`),
|
() => console.log(`Copied\n${copyText}`),
|
||||||
() => console.log(`Copied Failed\n${copyText}`)
|
() => console.log(`Copied Failed\n${copyText}`)
|
||||||
);
|
);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.toggleBlur();
|
toggleBlur();
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
show,
|
||||||
|
copyToolTip,
|
||||||
|
textToClipboard,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,10 +10,13 @@
|
||||||
</v-form>
|
</v-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
const UPLOAD_EVENT = "uploaded";
|
const UPLOAD_EVENT = "uploaded";
|
||||||
export default {
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
small: {
|
small: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -48,65 +51,70 @@ export default {
|
||||||
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();
|
const api = useUserApi();
|
||||||
|
async function upload() {
|
||||||
|
if (file.value != null) {
|
||||||
|
isSelecting.value = true;
|
||||||
|
|
||||||
return { api };
|
if (!props.post) {
|
||||||
},
|
context.emit(UPLOAD_EVENT, file.value);
|
||||||
data: () => ({
|
isSelecting.value = false;
|
||||||
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;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
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) {
|
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(
|
window.addEventListener(
|
||||||
"focus",
|
"focus",
|
||||||
() => {
|
() => {
|
||||||
this.isSelecting = false;
|
isSelecting.value = false;
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
|
uploader.value?.click();
|
||||||
|
}
|
||||||
|
|
||||||
this.$refs.uploader.click();
|
return {
|
||||||
},
|
file,
|
||||||
onFileChanged(e) {
|
uploader,
|
||||||
this.file = e.target.files[0];
|
isSelecting,
|
||||||
this.upload();
|
effIcon,
|
||||||
},
|
defaultText,
|
||||||
|
onFileChanged,
|
||||||
|
onButtonClick,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|
|
@ -20,8 +20,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -40,15 +42,15 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
setup(props) {
|
||||||
size() {
|
const size = computed(() => {
|
||||||
if (this.small) {
|
if (props.small) {
|
||||||
return {
|
return {
|
||||||
width: 2,
|
width: 2,
|
||||||
icon: 30,
|
icon: 30,
|
||||||
size: 50,
|
size: 50,
|
||||||
};
|
};
|
||||||
} else if (this.large) {
|
} else if (props.large) {
|
||||||
return {
|
return {
|
||||||
width: 4,
|
width: 4,
|
||||||
icon: 120,
|
icon: 120,
|
||||||
|
@ -60,10 +62,15 @@ export default {
|
||||||
icon: 75,
|
icon: 75,
|
||||||
size: 125,
|
size: 125,
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
waitingText() {
|
|
||||||
return this.$t("general.loading-recipes");
|
const { i18n } = useContext();
|
||||||
},
|
const waitingText = i18n.t("general.loading-recipes");
|
||||||
|
|
||||||
|
return {
|
||||||
|
size,
|
||||||
|
waitingText,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -137,14 +137,15 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||||
import { validators } from "@/composables/use-validators";
|
import { validators } from "@/composables/use-validators";
|
||||||
import { fieldTypes } from "@/composables/forms";
|
import { fieldTypes } from "@/composables/forms";
|
||||||
|
import { AutoFormItems } from "~/types/auto-forms";
|
||||||
|
|
||||||
const BLUR_EVENT = "blur";
|
const BLUR_EVENT = "blur";
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
name: "AutoForm",
|
name: "AutoForm",
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
@ -157,7 +158,7 @@ export default {
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
default: null,
|
default: null,
|
||||||
type: Array,
|
type: Array as () => AutoFormItems,
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
|
@ -165,7 +166,7 @@ export default {
|
||||||
},
|
},
|
||||||
globalRules: {
|
globalRules: {
|
||||||
default: null,
|
default: null,
|
||||||
type: Array,
|
type: Array as () => string[],
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
default: null,
|
default: null,
|
||||||
|
@ -176,94 +177,53 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props, context) {
|
||||||
const menu = ref({});
|
function rulesByKey(keys?: string[] | null) {
|
||||||
|
if (keys === undefined || keys === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
const list = [] as ((v: string) => (boolean | string))[];
|
||||||
menu,
|
keys.forEach((key) => {
|
||||||
fieldTypes,
|
if (key in validators) {
|
||||||
validators,
|
list.push(validators[key]);
|
||||||
};
|
|
||||||
},
|
|
||||||
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 {
|
return list;
|
||||||
if (this.value[val[i].varName]) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (val[i].type === "text" || val[i].type === "textarea") {
|
const defaultRules = computed(() => rulesByKey(props.globalRules));
|
||||||
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);
|
function removeByIndex(list: never[], index: number) {
|
||||||
} 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
|
// Removes the item at the index
|
||||||
list.splice(index, 1);
|
list.splice(index, 1);
|
||||||
},
|
}
|
||||||
getTemplate(item) {
|
|
||||||
const obj = {};
|
function getTemplate(item: AutoFormItems) {
|
||||||
|
const obj = {} as { [key: string]: string };
|
||||||
|
|
||||||
item.forEach((field) => {
|
item.forEach((field) => {
|
||||||
obj[field.varName] = "";
|
obj[field.varName] = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
},
|
}
|
||||||
rulesByKey(keys) {
|
|
||||||
const list = [];
|
|
||||||
|
|
||||||
if (keys === undefined) {
|
function emitBlur() {
|
||||||
return list;
|
context.emit(BLUR_EVENT, props.value);
|
||||||
}
|
}
|
||||||
if (keys === null) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
if (keys === list) {
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.forEach((key) => {
|
return {
|
||||||
if (key in this.validators) {
|
rulesByKey,
|
||||||
list.push(this.validators[key]);
|
defaultRules,
|
||||||
}
|
removeByIndex,
|
||||||
});
|
getTemplate,
|
||||||
return list;
|
emitBlur,
|
||||||
},
|
fieldTypes,
|
||||||
emitBlur() {
|
validators,
|
||||||
this.$emit(BLUR_EVENT, this.value);
|
};
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<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>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
export default {
|
|
||||||
|
export default defineComponent({
|
||||||
name: "BaseButton",
|
name: "BaseButton",
|
||||||
props: {
|
props: {
|
||||||
// Types
|
// Types
|
||||||
|
@ -106,103 +108,99 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props) {
|
||||||
const api = useUserApi();
|
const { $globals } = useContext();
|
||||||
|
const buttonOptions = {
|
||||||
return { api };
|
create: {
|
||||||
},
|
text: "Create",
|
||||||
data() {
|
icon: $globals.icons.createAlt,
|
||||||
return {
|
color: "success",
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
buttonStyles: {
|
update: {
|
||||||
defaults: {
|
text: "Update",
|
||||||
text: false,
|
icon: $globals.icons.edit,
|
||||||
outlined: false,
|
color: "success",
|
||||||
},
|
},
|
||||||
secondary: {
|
save: {
|
||||||
text: false,
|
text: "Save",
|
||||||
outlined: true,
|
icon: $globals.icons.save,
|
||||||
},
|
color: "success",
|
||||||
minor: {
|
},
|
||||||
outlined: false,
|
edit: {
|
||||||
text: true,
|
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: {
|
const btnAttrs = computed(() => {
|
||||||
btnAttrs() {
|
if (props.delete) {
|
||||||
if (this.delete) {
|
return buttonOptions.delete;
|
||||||
return this.buttonOptions.delete;
|
} else if (props.update) {
|
||||||
} else if (this.update) {
|
return buttonOptions.update;
|
||||||
return this.buttonOptions.update;
|
} else if (props.edit) {
|
||||||
} else if (this.edit) {
|
return buttonOptions.edit;
|
||||||
return this.buttonOptions.edit;
|
} else if (props.cancel) {
|
||||||
} else if (this.cancel) {
|
return buttonOptions.cancel;
|
||||||
this.setMinor();
|
} else if (props.save) {
|
||||||
return this.buttonOptions.cancel;
|
return buttonOptions.save;
|
||||||
} else if (this.save) {
|
} else if (props.download) {
|
||||||
return this.buttonOptions.save;
|
return buttonOptions.download;
|
||||||
} else if (this.download) {
|
|
||||||
return this.buttonOptions.download;
|
|
||||||
}
|
}
|
||||||
return this.buttonOptions.create;
|
return buttonOptions.create;
|
||||||
},
|
});
|
||||||
btnStyle() {
|
|
||||||
if (this.secondary) {
|
const buttonStyles = {
|
||||||
return this.buttonStyles.secondary;
|
defaults: {
|
||||||
} else if (this.minor) {
|
text: false,
|
||||||
return this.buttonStyles.minor;
|
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>
|
</script>
|
||||||
|
|
|
@ -23,8 +23,10 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -39,5 +41,5 @@ export default {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</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) {
|
setup(props, context) {
|
||||||
const dialog = computed<Boolean>({
|
const dialog = computed<boolean>({
|
||||||
get() {
|
get() {
|
||||||
return props.value;
|
return props.value;
|
||||||
},
|
},
|
||||||
|
@ -129,12 +129,9 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
determineClose(): Boolean {
|
determineClose(): boolean {
|
||||||
return this.submitted && !this.loading && !this.keepOpen;
|
return this.submitted && !this.loading && !this.keepOpen;
|
||||||
},
|
},
|
||||||
displayicon(): Boolean {
|
|
||||||
return this.icon || this.$globals.icons.user;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
determineClose() {
|
determineClose() {
|
||||||
|
@ -181,4 +178,4 @@ export default defineComponent({
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
<v-divider :width="width" :class="color" :style="`border-width: ${thickness} !important`" />
|
<v-divider :width="width" :class="color" :style="`border-width: ${thickness} !important`" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
width: {
|
width: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -18,5 +20,5 @@ export default {
|
||||||
default: "accent",
|
default: "accent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -17,13 +17,15 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
divider: {
|
divider: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
w<template>
|
<template>
|
||||||
<v-card v-bind="$attrs" :class="classes" class="v-card--material pa-3">
|
<v-card v-bind="$attrs" :class="classes" class="v-card--material pa-3">
|
||||||
<div class="d-flex grow flex-wrap">
|
<div class="d-flex grow flex-wrap">
|
||||||
<slot name="avatar">
|
<slot name="avatar">
|
||||||
|
@ -40,8 +40,10 @@ w<template>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
name: "MaterialCard",
|
name: "MaterialCard",
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
@ -70,22 +72,25 @@ export default {
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const { $vuetify } = useContext();
|
||||||
|
|
||||||
computed: {
|
const hasHeading = computed(() => false);
|
||||||
classes() {
|
const hasAltHeading = computed(() => false);
|
||||||
|
const classes = computed(() => {
|
||||||
return {
|
return {
|
||||||
"v-card--material--has-heading": this.hasHeading,
|
"v-card--material--has-heading": hasHeading,
|
||||||
"mt-3": this.$vuetify.breakpoint.name === "xs" || this.$vuetify.breakpoint.name === "sm",
|
"mt-3": $vuetify.breakpoint.name === "xs" || $vuetify.breakpoint.name === "sm",
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
hasHeading() {
|
|
||||||
return false;
|
return {
|
||||||
},
|
hasHeading,
|
||||||
hasAltHeading() {
|
hasAltHeading,
|
||||||
return false;
|
classes,
|
||||||
},
|
};
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
></VJsoneditor>
|
></VJsoneditor>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
// @ts-ignore
|
||||||
import VJsoneditor from "v-jsoneditor";
|
import VJsoneditor from "v-jsoneditor";
|
||||||
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
components: { VJsoneditor },
|
components: { VJsoneditor },
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
@ -23,6 +25,6 @@ export default {
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default defineComponent({
|
||||||
{ text: "Delete", value: "actions" },
|
{ text: "Delete", value: "actions" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function handleRowClick(item: any) {
|
function handleRowClick(item: ReportSummary) {
|
||||||
router.push("/user/group/data/reports/" + item.id);
|
router.push("/user/group/data/reports/" + item.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,4 +70,4 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,30 +2,26 @@ import { AxiosResponse } from "axios";
|
||||||
import { useContext } from "@nuxtjs/composition-api";
|
import { useContext } from "@nuxtjs/composition-api";
|
||||||
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
||||||
import { AdminAPI, Api } from "~/api";
|
import { AdminAPI, Api } from "~/api";
|
||||||
import { ApiRequestInstance } from "~/types/api";
|
import { ApiRequestInstance, RequestResponse } from "~/types/api";
|
||||||
|
|
||||||
interface RequestResponse<T> {
|
|
||||||
response: AxiosResponse<T> | null;
|
|
||||||
data: T | null;
|
|
||||||
error: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
async safe<T>(funcCall: any, url: string, data: object = {}): Promise<RequestResponse<T>> {
|
async safe<T, U>(funcCall: (url: string, data: U) => Promise<AxiosResponse<T>>, url: string, data: U): Promise<RequestResponse<T>> {
|
||||||
const response = await funcCall(url, data).catch(function (error: object) {
|
let error = null;
|
||||||
console.log(error);
|
const response = await funcCall(url, data).catch(function (e) {
|
||||||
|
console.log(e);
|
||||||
// Insert Generic Error Handling Here
|
// Insert Generic Error Handling Here
|
||||||
return { response: null, error, data: null };
|
error = e;
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
return { response, error: null, data: response.data };
|
return { response, error, data: response?.data ?? null };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getRequests(axoisInstance: NuxtAxiosInstance): ApiRequestInstance {
|
function getRequests(axiosInstance: NuxtAxiosInstance): ApiRequestInstance {
|
||||||
const requests = {
|
return {
|
||||||
async get<T>(url: string, params = {}): Promise<RequestResponse<T>> {
|
async get<T>(url: string, params = {}): Promise<RequestResponse<T>> {
|
||||||
let error = null;
|
let error = null;
|
||||||
const response = await axoisInstance.get<T>(url, params).catch((e) => {
|
const response = await axiosInstance.get<T>(url, params).catch((e) => {
|
||||||
error = e;
|
error = e;
|
||||||
});
|
});
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
@ -34,23 +30,26 @@ function getRequests(axoisInstance: NuxtAxiosInstance): ApiRequestInstance {
|
||||||
return { response: null, error, data: null };
|
return { response: null, error, data: null };
|
||||||
},
|
},
|
||||||
|
|
||||||
async post<T>(url: string, data: object) {
|
async post<T, U>(url: string, data: U) {
|
||||||
return await request.safe<T>(axoisInstance.post, url, data);
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
return await request.safe<T, U>(axiosInstance.post, url, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async put<T>(url: string, data: object) {
|
async put<T, U = T>(url: string, data: U) {
|
||||||
return await request.safe<T>(axoisInstance.put, url, data);
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
return await request.safe<T, U>(axiosInstance.put, url, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async patch<T>(url: string, data: object) {
|
async patch<T, U = Partial<T>>(url: string, data: U) {
|
||||||
return await request.safe<T>(axoisInstance.patch, url, data);
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
return await request.safe<T, U>(axiosInstance.patch, url, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async delete<T>(url: string) {
|
async delete<T>(url: string) {
|
||||||
return await request.safe<T>(axoisInstance.delete, url);
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
return await request.safe<T, undefined>(axiosInstance.delete, url, undefined);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return requests;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAdminApi = function (): AdminAPI {
|
export const useAdminApi = function (): AdminAPI {
|
||||||
|
|
|
@ -5,20 +5,20 @@ export const useStaticRoutes = () => {
|
||||||
const { $config, req } = useContext();
|
const { $config, req } = useContext();
|
||||||
const serverBase = detectServerBaseUrl(req);
|
const serverBase = detectServerBaseUrl(req);
|
||||||
|
|
||||||
const prefix = `${$config.SUB_PATH}/api`.replace("//", "/");
|
const prefix = `${$config.SUB_PATH as string}/api`.replace("//", "/");
|
||||||
|
|
||||||
const fullBase = serverBase + prefix;
|
const fullBase = serverBase + prefix;
|
||||||
|
|
||||||
// Methods to Generate reference urls for assets/images *
|
// Methods to Generate reference urls for assets/images *
|
||||||
function recipeImage(recipeSlug: string, version = null, key = null) {
|
function recipeImage(recipeSlug: string, version = "", key = 1) {
|
||||||
return `${fullBase}/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`;
|
return `${fullBase}/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function recipeSmallImage(recipeSlug: string, version = null, key = null) {
|
function recipeSmallImage(recipeSlug: string, version = "", key = 1) {
|
||||||
return `${fullBase}/media/recipes/${recipeSlug}/images/min-original.webp?&rnd=${key}&version=${version}`;
|
return `${fullBase}/media/recipes/${recipeSlug}/images/min-original.webp?&rnd=${key}&version=${version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function recipeTinyImage(recipeSlug: string, version = null, key = null) {
|
function recipeTinyImage(recipeSlug: string, version = "", key = 1) {
|
||||||
return `${fullBase}/media/recipes/${recipeSlug}/images/tiny-original.webp?&rnd=${key}&version=${version}`;
|
return `${fullBase}/media/recipes/${recipeSlug}/images/tiny-original.webp?&rnd=${key}&version=${version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* frac.js (C) 2012-present SheetJS -- http://sheetjs.com */
|
/* frac.js (C) 2012-present SheetJS -- http://sheetjs.com */
|
||||||
/* https://developer.aliyun.com/mirror/npm/package/frac/v/0.3.0 Apache license */
|
/* https://developer.aliyun.com/mirror/npm/package/frac/v/0.3.0 Apache license */
|
||||||
|
|
||||||
function frac(x: number, D: number, mixed: Boolean) {
|
function frac(x: number, D: number, mixed: boolean) {
|
||||||
let n1 = Math.floor(x);
|
let n1 = Math.floor(x);
|
||||||
let d1 = 1;
|
let d1 = 1;
|
||||||
let n2 = n1 + 1;
|
let n2 = n1 + 1;
|
||||||
|
@ -33,7 +33,7 @@ function frac(x: number, D: number, mixed: Boolean) {
|
||||||
const q = Math.floor(n1 / d1);
|
const q = Math.floor(n1 / d1);
|
||||||
return [q, n1 - q * d1, d1];
|
return [q, n1 - q * d1, d1];
|
||||||
}
|
}
|
||||||
function cont(x: number, D: number, mixed: Boolean) {
|
function cont(x: number, D: number, mixed: boolean) {
|
||||||
const sgn = x < 0 ? -1 : 1;
|
const sgn = x < 0 ? -1 : 1;
|
||||||
let B = x * sgn;
|
let B = x * sgn;
|
||||||
let P_2 = 0;
|
let P_2 = 0;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api";
|
||||||
import { useAsyncKey } from "../use-utils";
|
import { useAsyncKey } from "../use-utils";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { Food } from "~/api/class-interfaces/recipe-foods";
|
import { Food } from "~/api/class-interfaces/recipe-foods";
|
||||||
|
import { VForm} from "~/types/vuetify";
|
||||||
|
|
||||||
let foodStore: Ref<Food[] | null> | null = null;
|
let foodStore: Ref<Food[] | null> | null = null;
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,28 @@ import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
const { frac } = useFraction();
|
const { frac } = useFraction();
|
||||||
|
|
||||||
export function parseIngredientText(ingredient: RecipeIngredient, disableAmount: boolean, scale: number = 1): string {
|
export function parseIngredientText(ingredient: RecipeIngredient, disableAmount: boolean, scale = 1): string {
|
||||||
if (disableAmount) {
|
if (disableAmount) {
|
||||||
return ingredient.note;
|
return ingredient.note || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const { quantity, food, unit, note } = ingredient;
|
const { quantity, food, unit, note } = ingredient;
|
||||||
|
|
||||||
const validQuantity = quantity !== null && quantity !== undefined && quantity !== 0;
|
|
||||||
|
|
||||||
let returnQty = "";
|
let returnQty = "";
|
||||||
if (unit?.fraction) {
|
if (quantity !== undefined && quantity !== 0) {
|
||||||
const fraction = frac(quantity * scale, 10, true);
|
if (unit?.fraction) {
|
||||||
if (fraction[0] !== undefined && fraction[0] > 0) {
|
const fraction = frac(quantity * scale, 10, true);
|
||||||
returnQty += fraction[0];
|
if (fraction[0] !== undefined && fraction[0] > 0) {
|
||||||
}
|
returnQty += fraction[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (fraction[1] > 0) {
|
if (fraction[1] > 0) {
|
||||||
returnQty += ` <sup>${fraction[1]}</sup>⁄<sub>${fraction[2]}</sub>`;
|
returnQty += ` <sup>${fraction[1]}</sup>⁄<sub>${fraction[2]}</sub>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnQty = (quantity * scale).toString();
|
||||||
}
|
}
|
||||||
} else if (validQuantity) {
|
|
||||||
returnQty = (quantity * scale).toString();
|
|
||||||
} else {
|
|
||||||
returnQty = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${returnQty} ${unit?.name || " "} ${food?.name || " "} ${note}`.replace(/ {2,}/g, " ");
|
return `${returnQty} ${unit?.name || " "} ${food?.name || " "} ${note || " "}`.replace(/ {2,}/g, " ");
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const useRecipeMeta = (recipe: Ref<Recipe>) => {
|
||||||
{
|
{
|
||||||
hid: "og:desc",
|
hid: "og:desc",
|
||||||
property: "og:description",
|
property: "og:description",
|
||||||
content: recipe?.value?.description,
|
content: recipe?.value?.description ?? "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hid: "og-image",
|
hid: "og-image",
|
||||||
|
@ -25,12 +25,12 @@ export const useRecipeMeta = (recipe: Ref<Recipe>) => {
|
||||||
{
|
{
|
||||||
hid: "twitter:title",
|
hid: "twitter:title",
|
||||||
property: "twitter:title",
|
property: "twitter:title",
|
||||||
content: recipe?.value?.name,
|
content: recipe?.value?.name ?? "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hid: "twitter:desc",
|
hid: "twitter:desc",
|
||||||
property: "twitter:description",
|
property: "twitter:description",
|
||||||
content: recipe?.value?.description,
|
content: recipe?.value?.description ?? "",
|
||||||
},
|
},
|
||||||
{ hid: "t-type", name: "twitter:card", content: "summary_large_image" },
|
{ hid: "t-type", name: "twitter:card", content: "summary_large_image" },
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
|
import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
|
||||||
import { useAsyncKey } from "../use-utils";
|
import { useAsyncKey } from "../use-utils";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export const useTools = function (eager = true) {
|
export const useTools = function (eager = true) {
|
||||||
const workingToolData = reactive({
|
const workingToolData = reactive({
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api";
|
||||||
import { useAsyncKey } from "../use-utils";
|
import { useAsyncKey } from "../use-utils";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { Unit } from "~/api/class-interfaces/recipe-units";
|
import { Unit } from "~/api/class-interfaces/recipe-units";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
let unitStore: Ref<Unit[] | null> | null = null;
|
let unitStore: Ref<Unit[] | null> | null = null;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ref, onMounted } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { Recipe } from "~/types/api-types/recipe";
|
import { Recipe } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
export const useRecipe = function (slug: string, eager: boolean = true) {
|
export const useRecipe = function (slug: string, eager = true) {
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const recentRecipes = ref<Recipe[] | null>([]);
|
||||||
|
|
||||||
const rand = (n: number) => Math.floor(Math.random() * n);
|
const rand = (n: number) => Math.floor(Math.random() * n);
|
||||||
|
|
||||||
function swap(t: Array<any>, i: number, j: number) {
|
function swap(t: Array<unknown>, i: number, j: number) {
|
||||||
const q = t[i];
|
const q = t[i];
|
||||||
t[i] = t[j];
|
t[i] = t[j];
|
||||||
t[j] = q;
|
t[j] = q;
|
||||||
|
@ -19,19 +19,19 @@ function swap(t: Array<any>, i: number, j: number) {
|
||||||
export const useSorter = () => {
|
export const useSorter = () => {
|
||||||
function sortAToZ(list: Array<Recipe>) {
|
function sortAToZ(list: Array<Recipe>) {
|
||||||
list.sort((a, b) => {
|
list.sort((a, b) => {
|
||||||
const textA = a.name.toUpperCase();
|
const textA = a.name?.toUpperCase() ?? "";
|
||||||
const textB = b.name.toUpperCase();
|
const textB = b.name?.toUpperCase() ?? "";
|
||||||
return textA < textB ? -1 : textA > textB ? 1 : 0;
|
return textA < textB ? -1 : textA > textB ? 1 : 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function sortByCreated(list: Array<Recipe>) {
|
function sortByCreated(list: Array<Recipe>) {
|
||||||
list.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1));
|
list.sort((a, b) => ((a.dateAdded ?? "") > (b.dateAdded ?? "") ? -1 : 1));
|
||||||
}
|
}
|
||||||
function sortByUpdated(list: Array<Recipe>) {
|
function sortByUpdated(list: Array<Recipe>) {
|
||||||
list.sort((a, b) => (a.dateUpdated > b.dateUpdated ? -1 : 1));
|
list.sort((a, b) => ((a.dateUpdated ?? "") > (b.dateUpdated ?? "") ? -1 : 1));
|
||||||
}
|
}
|
||||||
function sortByRating(list: Array<Recipe>) {
|
function sortByRating(list: Array<Recipe>) {
|
||||||
list.sort((a, b) => (a.rating > b.rating ? -1 : 1));
|
list.sort((a, b) => ((a.rating ?? 0) > (b.rating ?? 0) ? -1 : 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomRecipe(list: Array<Recipe>): Recipe {
|
function randomRecipe(list: Array<Recipe>): Recipe {
|
||||||
|
|
|
@ -60,8 +60,7 @@ export const useCookbooks = function () {
|
||||||
async createOne() {
|
async createOne() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await api.cookbooks.createOne({
|
const { data } = await api.cookbooks.createOne({
|
||||||
// @ts-ignore. I"m thinking this will always be defined.
|
name: "Cookbook " + String((cookbookStore?.value?.length ?? 0) + 1),
|
||||||
name: "Cookbook " + String(cookbookStore?.value?.length + 1 || 1),
|
|
||||||
});
|
});
|
||||||
if (data && cookbookStore?.value) {
|
if (data && cookbookStore?.value) {
|
||||||
cookbookStore.value.push(data);
|
cookbookStore.value.push(data);
|
||||||
|
|
|
@ -40,7 +40,7 @@ export const useMealplans = function (range: Ref<DateRange>) {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
return units;
|
return units;
|
||||||
},
|
},
|
||||||
async refreshAll() {
|
async refreshAll(this: void) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const query = {
|
const query = {
|
||||||
start: format(range.value.start, "yyyy-MM-dd"),
|
start: format(range.value.start, "yyyy-MM-dd"),
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import { IncomingMessage } from "connect";
|
||||||
|
|
||||||
export const useAsyncKey = function () {
|
export const useAsyncKey = function () {
|
||||||
return String(Date.now());
|
return String(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
export function detectServerBaseUrl(req: any) {
|
export function detectServerBaseUrl(req?: IncomingMessage | null) {
|
||||||
if (!req || req === undefined) {
|
if (!req || req === undefined) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -10,26 +12,27 @@ export function detectServerBaseUrl(req: any) {
|
||||||
const url = new URL(req.headers.referer);
|
const url = new URL(req.headers.referer);
|
||||||
return `${url.protocol}//${url.host}`;
|
return `${url.protocol}//${url.host}`;
|
||||||
} else if (req.headers.host) {
|
} else if (req.headers.host) {
|
||||||
const protocol = req.connection.encrypted ? "https" : "http:";
|
// TODO Socket.encrypted doesn't exist. What is needed here?
|
||||||
|
// @ts-ignore
|
||||||
|
const protocol = req.socket.encrypted ? "https:" : "http:";
|
||||||
return `${protocol}//${req.headers.host}`;
|
return `${protocol}//${req.headers.host}`;
|
||||||
} else if (req.connection.remoteAddress) {
|
} else if (req.socket.remoteAddress) {
|
||||||
const protocol = req.connection.encrypted ? "https" : "http:";
|
// @ts-ignore
|
||||||
return `${protocol}//${req.connection.localAddress}:${req.connection.localPort}`;
|
const protocol = req.socket.encrypted ? "https:" : "http:";
|
||||||
|
return `${protocol}//${req.socket.localAddress}:${req.socket.localPort}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uuid4() {
|
export function uuid4() {
|
||||||
// @ts-ignore
|
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
|
||||||
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
(parseInt(c) ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (parseInt(c) / 4)))).toString(16)
|
||||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/28876300/deep-copying-array-of-nested-objects-in-javascript
|
// https://stackoverflow.com/questions/28876300/deep-copying-array-of-nested-objects-in-javascript
|
||||||
const toString = Object.prototype.toString;
|
export function deepCopy<T>(obj: T): T {
|
||||||
export function deepCopy(obj: any) {
|
|
||||||
let rv;
|
let rv;
|
||||||
|
|
||||||
switch (typeof obj) {
|
switch (typeof obj) {
|
||||||
|
@ -38,19 +41,19 @@ export function deepCopy(obj: any) {
|
||||||
// null => null
|
// null => null
|
||||||
rv = null;
|
rv = null;
|
||||||
} else {
|
} else {
|
||||||
switch (toString.call(obj)) {
|
switch (Object.prototype.toString.call(obj)) {
|
||||||
case "[object Array]":
|
case "[object Array]":
|
||||||
// It's an array, create a new array with
|
// It's an array, create a new array with
|
||||||
// deep copies of the entries
|
// deep copies of the entries
|
||||||
rv = obj.map(deepCopy);
|
rv = (obj as unknown as Array<unknown>).map(deepCopy);
|
||||||
break;
|
break;
|
||||||
case "[object Date]":
|
case "[object Date]":
|
||||||
// Clone the date
|
// Clone the date
|
||||||
rv = new Date(obj);
|
rv = new Date(obj as unknown as Date);
|
||||||
break;
|
break;
|
||||||
case "[object RegExp]":
|
case "[object RegExp]":
|
||||||
// Clone the RegExp
|
// Clone the RegExp
|
||||||
rv = new RegExp(obj);
|
rv = new RegExp(obj as unknown as RegExp);
|
||||||
break;
|
break;
|
||||||
// ...probably a few others
|
// ...probably a few others
|
||||||
default:
|
default:
|
||||||
|
@ -70,5 +73,5 @@ export function deepCopy(obj: any) {
|
||||||
rv = obj;
|
rv = obj;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return rv;
|
return rv as T;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,12 @@ const EMAIL_REGEX =
|
||||||
|
|
||||||
const URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
const URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
|
||||||
|
|
||||||
export const validators = {
|
export const validators: {[key: string]: (v: string) => boolean | string} = {
|
||||||
required: (v: string) => !!v || "This Field is Required",
|
required: (v: string) => !!v || "This Field is Required",
|
||||||
email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
|
email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
|
||||||
whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed",
|
whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed",
|
||||||
url: (v: string) => !v || URL_REGEX.test(v) || "Must Be A Valid URL",
|
url: (v: string) => !v || URL_REGEX.test(v) || "Must Be A Valid URL",
|
||||||
minLength: (min: number) => (v: string) => !v || v.length >= min || `Must Be At Least ${min} Characters`,
|
// TODO These appear to be unused?
|
||||||
maxLength: (max: number) => (v: string) => !v || v.length <= max || `Must Be At Most ${max} Characters`,
|
// minLength: (min: number) => (v: string) => !v || v.length >= min || `Must Be At Least ${min} Characters`,
|
||||||
|
// maxLength: (max: number) => (v: string) => !v || v.length <= max || `Must Be At Most ${max} Characters`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</v-main>
|
</v-main>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, useContext, onMounted } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, useContext, onMounted } from "@nuxtjs/composition-api";
|
||||||
|
@ -37,7 +37,6 @@ export default defineComponent({
|
||||||
middleware: "auth",
|
middleware: "auth",
|
||||||
auth: true,
|
auth: true,
|
||||||
setup() {
|
setup() {
|
||||||
// @ts-ignore - $globals not found in type definition
|
|
||||||
const { $globals, i18n, $vuetify } = useContext();
|
const { $globals, i18n, $vuetify } = useContext();
|
||||||
|
|
||||||
const sidebar = ref<boolean | null>(null);
|
const sidebar = ref<boolean | null>(null);
|
||||||
|
@ -124,4 +123,3 @@ export default defineComponent({
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
</v-main>
|
</v-main>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
|
@ -74,7 +74,6 @@ export default defineComponent({
|
||||||
middleware: "auth",
|
middleware: "auth",
|
||||||
setup() {
|
setup() {
|
||||||
const { cookbooks } = useCookbooks();
|
const { cookbooks } = useCookbooks();
|
||||||
// @ts-ignore
|
|
||||||
const { $globals, $auth, $vuetify } = useContext();
|
const { $globals, $auth, $vuetify } = useContext();
|
||||||
|
|
||||||
const isAdmin = computed(() => $auth.user?.admin);
|
const isAdmin = computed(() => $auth.user?.admin);
|
||||||
|
@ -87,7 +86,7 @@ export default defineComponent({
|
||||||
console.log("toggleDark");
|
console.log("toggleDark");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sidebar = ref<Boolean | null>(null);
|
const sidebar = ref<boolean | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
sidebar.value = !$vuetify.breakpoint.md;
|
sidebar.value = !$vuetify.breakpoint.md;
|
||||||
|
@ -190,4 +189,4 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,8 +27,10 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
export default {
|
import { defineComponent, useContext, useMeta } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
layout: "basic",
|
layout: "basic",
|
||||||
props: {
|
props: {
|
||||||
error: {
|
error: {
|
||||||
|
@ -36,28 +38,23 @@ export default {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
setup(props) {
|
||||||
|
useMeta({ title: props.error.statusCode === 404 ? "404 Not Found" : "An error occurred" });
|
||||||
|
|
||||||
|
const { $globals, i18n } = useContext();
|
||||||
|
const buttons = [
|
||||||
|
{ icon: $globals.icons.home, to: "/", text: i18n.t("general.home") },
|
||||||
|
{ icon: $globals.icons.primary, to: "/recipes/all", text: i18n.t("page.all-recipes") },
|
||||||
|
{ icon: $globals.icons.search, to: "/search", text: i18n.t("search.search") },
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageNotFound: "404 Not Found",
|
buttons,
|
||||||
otherError: "An error occurred",
|
}
|
||||||
};
|
|
||||||
},
|
},
|
||||||
head() {
|
// Needed for useMeta
|
||||||
const title = this.error.statusCode === 404 ? this.pageNotFound : this.otherError;
|
head: {},
|
||||||
return {
|
});
|
||||||
title,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
buttons() {
|
|
||||||
return [
|
|
||||||
{ icon: this.$globals.icons.home, to: "/", text: this.$t("general.home") },
|
|
||||||
{ icon: this.$globals.icons.primary, to: "/recipes/all", text: this.$t("page.all-recipes") },
|
|
||||||
{ icon: this.$globals.icons.search, to: "/search", text: this.$t("search.search") },
|
|
||||||
];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"start": "nuxt start",
|
"start": "nuxt start",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
"lint:js": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore .",
|
||||||
"lint": "yarn lint:js"
|
"lint": "yarn lint:js"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,vue}": "eslint"
|
"*.{ts,js,vue}": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adapttive/vue-markdown": "^4.0.1",
|
"@adapttive/vue-markdown": "^4.0.1",
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { useGroups } from "~/composables/use-groups";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { useUserForm } from "~/composables/use-users";
|
import { useUserForm } from "~/composables/use-users";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -126,7 +126,7 @@ export default defineComponent({
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
function handleRowClick(item: Group) {
|
function handleRowClick(item: Group) {
|
||||||
router.push("/admin/manage/groups/" + item.id);
|
router.push(`/admin/manage/groups/${item.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...toRefs(state), groups, refreshAllGroups, deleteGroup, createGroup, openDialog, handleRowClick };
|
return { ...toRefs(state), groups, refreshAllGroups, deleteGroup, createGroup, openDialog, handleRowClick };
|
||||||
|
|
|
@ -44,6 +44,7 @@ import { useGroups } from "~/composables/use-groups";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { useUserForm } from "~/composables/use-users";
|
import { useUserForm } from "~/composables/use-users";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { useAdminApi } from "~/composables/api";
|
||||||
import { useGroups } from "~/composables/use-groups";
|
import { useGroups } from "~/composables/use-groups";
|
||||||
import { useUserForm } from "~/composables/use-users";
|
import { useUserForm } from "~/composables/use-users";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
|
@ -92,4 +93,4 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default defineComponent({
|
||||||
const { loading, deleteUser } = useUser(refreshAllUsers);
|
const { loading, deleteUser } = useUser(refreshAllUsers);
|
||||||
|
|
||||||
function handleRowClick(item: UserOut) {
|
function handleRowClick(item: UserOut) {
|
||||||
router.push("/admin/manage/users/" + item.id);
|
router.push(`/admin/manage/users/${item.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
// ==========================================================
|
||||||
|
@ -114,4 +114,4 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
</section>
|
</section>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
|
@ -199,7 +199,7 @@ export default defineComponent({
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getColor(booly: boolean | any, warning = false) {
|
function getColor(booly: unknown, warning = false) {
|
||||||
const falsey = warning ? "warning" : "error";
|
const falsey = warning ? "warning" : "error";
|
||||||
return booly ? "success" : falsey;
|
return booly ? "success" : falsey;
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,6 @@ export default defineComponent({
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// General About Info
|
// General About Info
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const { $globals, i18n } = useContext();
|
const { $globals, i18n } = useContext();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -295,6 +294,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -144,14 +144,13 @@
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, useContext, toRefs, ref } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, useContext, toRefs, ref } from "@nuxtjs/composition-api";
|
||||||
import { useNotifications } from "@/composables/use-notifications";
|
import { useNotifications } from "@/composables/use-notifications";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
setup() {
|
setup() {
|
||||||
// @ts-ignore -> Ignore missing $globals
|
|
||||||
const { i18n } = useContext();
|
const { i18n } = useContext();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
@ -222,6 +221,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -209,7 +209,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const loggingIn = ref(false);
|
const loggingIn = ref(false);
|
||||||
|
|
||||||
const allowSignup = computed(() => context.env.ALLOW_SIGNUP);
|
const allowSignup = computed(() => context.env.ALLOW_SIGNUP as boolean);
|
||||||
|
|
||||||
async function authenticate() {
|
async function authenticate() {
|
||||||
loggingIn.value = true;
|
loggingIn.value = true;
|
||||||
|
@ -221,7 +221,11 @@ export default defineComponent({
|
||||||
try {
|
try {
|
||||||
await $auth.loginWith("local", { data: formData });
|
await $auth.loginWith("local", { data: formData });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response.status === 401) {
|
// TODO Check if error is an AxiosError, but isAxiosError is not working right now
|
||||||
|
// See https://github.com/nuxt-community/axios-module/issues/550
|
||||||
|
// Import $axios from useContext()
|
||||||
|
// if ($axios.isAxiosError(error) && error.response?.status === 401) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
alert.error("Invalid Credentials");
|
alert.error("Invalid Credentials");
|
||||||
} else {
|
} else {
|
||||||
alert.error("Something Went Wrong!");
|
alert.error("Something Went Wrong!");
|
||||||
|
@ -250,4 +254,4 @@ export default defineComponent({
|
||||||
.max-button {
|
.max-button {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -210,6 +210,7 @@ import { useMealplans, planTypeOptions } from "~/composables/use-group-mealplan"
|
||||||
import { useRecipes, allRecipes } from "~/composables/recipes";
|
import { useRecipes, allRecipes } from "~/composables/recipes";
|
||||||
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
|
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
|
||||||
import RecipeCard from "~/components/Domain/Recipe/RecipeCard.vue";
|
import RecipeCard from "~/components/Domain/Recipe/RecipeCard.vue";
|
||||||
|
import { PlanEntryType } from "~/types/api-types/meal-plan";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
@ -238,7 +239,7 @@ export default defineComponent({
|
||||||
useRecipes(true, true);
|
useRecipes(true, true);
|
||||||
|
|
||||||
function filterMealByDate(date: Date) {
|
function filterMealByDate(date: Date) {
|
||||||
if (!mealplans.value) return;
|
if (!mealplans.value) return [];
|
||||||
return mealplans.value.filter((meal) => {
|
return mealplans.value.filter((meal) => {
|
||||||
const mealDate = parseISO(meal.date);
|
const mealDate = parseISO(meal.date);
|
||||||
return isSameDay(mealDate, date);
|
return isSameDay(mealDate, date);
|
||||||
|
@ -263,14 +264,12 @@ export default defineComponent({
|
||||||
// The drop was cancelled, unsure if anything needs to be done?
|
// The drop was cancelled, unsure if anything needs to be done?
|
||||||
console.log("Cancel Move Event");
|
console.log("Cancel Move Event");
|
||||||
} else {
|
} else {
|
||||||
// A Meal was moved, set the new date value and make a update request and refresh the meals
|
// A Meal was moved, set the new date value and make an update request and refresh the meals
|
||||||
const fromMealsByIndex = evt.from.getAttribute("data-index");
|
const fromMealsByIndex = parseInt(evt.from.getAttribute("data-index") ?? "");
|
||||||
const toMealsByIndex = evt.to.getAttribute("data-index");
|
const toMealsByIndex = parseInt(evt.to.getAttribute("data-index") ?? "");
|
||||||
|
|
||||||
if (fromMealsByIndex) {
|
if (!isNaN(fromMealsByIndex) && !isNaN(toMealsByIndex)) {
|
||||||
// @ts-ignore
|
|
||||||
const mealData = mealsByDate.value[fromMealsByIndex].meals[evt.oldIndex as number];
|
const mealData = mealsByDate.value[fromMealsByIndex].meals[evt.oldIndex as number];
|
||||||
// @ts-ignore
|
|
||||||
const destDate = mealsByDate.value[toMealsByIndex].date;
|
const destDate = mealsByDate.value[toMealsByIndex].date;
|
||||||
|
|
||||||
mealData.date = format(destDate, "yyyy-MM-dd");
|
mealData.date = format(destDate, "yyyy-MM-dd");
|
||||||
|
@ -282,13 +281,12 @@ export default defineComponent({
|
||||||
|
|
||||||
const mealsByDate = computed(() => {
|
const mealsByDate = computed(() => {
|
||||||
return days.value.map((day) => {
|
return days.value.map((day) => {
|
||||||
return { date: day, meals: filterMealByDate(day as any) };
|
return { date: day, meals: filterMealByDate(day) };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const days = computed(() => {
|
const days = computed(() => {
|
||||||
return Array.from(Array(8).keys()).map(
|
return Array.from(Array(8).keys()).map(
|
||||||
// @ts-ignore
|
|
||||||
(i) => new Date(weekRange.value.start.getTime() + i * 24 * 60 * 60 * 1000)
|
(i) => new Date(weekRange.value.start.getTime() + i * 24 * 60 * 60 * 1000)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -304,7 +302,7 @@ export default defineComponent({
|
||||||
|
|
||||||
watch(dialog, () => {
|
watch(dialog, () => {
|
||||||
if (dialog.note) {
|
if (dialog.note) {
|
||||||
newMeal.recipeId = null;
|
newMeal.recipeId = undefined;
|
||||||
}
|
}
|
||||||
newMeal.title = "";
|
newMeal.title = "";
|
||||||
newMeal.text = "";
|
newMeal.text = "";
|
||||||
|
@ -314,13 +312,12 @@ export default defineComponent({
|
||||||
date: "",
|
date: "",
|
||||||
title: "",
|
title: "",
|
||||||
text: "",
|
text: "",
|
||||||
recipeId: null as Number | null,
|
recipeId: undefined as number | undefined,
|
||||||
entryType: "dinner",
|
entryType: "dinner" as PlanEntryType,
|
||||||
});
|
});
|
||||||
|
|
||||||
function openDialog(date: Date) {
|
function openDialog(date: Date) {
|
||||||
newMeal.date = format(date, "yyyy-MM-dd");
|
newMeal.date = format(date, "yyyy-MM-dd");
|
||||||
// @ts-ignore
|
|
||||||
state.createMealDialog = true;
|
state.createMealDialog = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,21 +326,20 @@ export default defineComponent({
|
||||||
newMeal.title = "";
|
newMeal.title = "";
|
||||||
newMeal.text = "";
|
newMeal.text = "";
|
||||||
newMeal.entryType = "dinner";
|
newMeal.entryType = "dinner";
|
||||||
newMeal.recipeId = null;
|
newMeal.recipeId = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function randomMeal(date: Date) {
|
async function randomMeal(date: Date) {
|
||||||
// TODO: Refactor to use API call to get random recipe
|
// TODO: Refactor to use API call to get random recipe
|
||||||
// @ts-ignore
|
const randomRecipe = allRecipes.value?.[Math.floor(Math.random() * allRecipes.value.length)];
|
||||||
const randomRecipe = allRecipes.value[Math.floor(Math.random() * allRecipes.value.length)];
|
if (!randomRecipe) return;
|
||||||
|
|
||||||
newMeal.date = format(date, "yyyy-MM-dd");
|
newMeal.date = format(date, "yyyy-MM-dd");
|
||||||
|
|
||||||
newMeal.recipeId = randomRecipe.id || null;
|
newMeal.recipeId = randomRecipe.id;
|
||||||
|
|
||||||
console.log(newMeal.recipeId, randomRecipe.id);
|
console.log(newMeal.recipeId, randomRecipe.id);
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
await actions.createOne({ ...newMeal });
|
await actions.createOne({ ...newMeal });
|
||||||
resetDialog();
|
resetDialog();
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,12 +94,12 @@ export default defineComponent({
|
||||||
|
|
||||||
const { recipeImage } = useStaticRoutes();
|
const { recipeImage } = useStaticRoutes();
|
||||||
|
|
||||||
function getIngredientByRefId(refId: String) {
|
function getIngredientByRefId(refId: string) {
|
||||||
if (!recipe.value) {
|
if (!recipe.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ing = recipe?.value.recipeIngredient.find((ing) => ing.referenceId === refId) || "";
|
const ing = recipe?.value.recipeIngredient?.find((ing) => ing.referenceId === refId) || "";
|
||||||
if (ing === "") {
|
if (ing === "") {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
:key="imageKey"
|
:key="imageKey"
|
||||||
:max-width="enableLandscape ? null : '50%'"
|
:max-width="enableLandscape ? null : '50%'"
|
||||||
:min-height="hideImage ? '50' : imageHeight"
|
:min-height="hideImage ? '50' : imageHeight"
|
||||||
:src="recipeImage(recipe.slug, imageKey)"
|
:src="recipeImage(recipe.slug, '', imageKey)"
|
||||||
class="d-print-none"
|
class="d-print-none"
|
||||||
@error="hideImage = true"
|
@error="hideImage = true"
|
||||||
>
|
>
|
||||||
|
@ -561,7 +561,6 @@ export default defineComponent({
|
||||||
|
|
||||||
const { recipeImage } = useStaticRoutes();
|
const { recipeImage } = useStaticRoutes();
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const { $vuetify } = useContext();
|
const { $vuetify } = useContext();
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
@ -623,7 +622,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
async function uploadImage(fileObject: File) {
|
async function uploadImage(fileObject: File) {
|
||||||
if (!recipe.value) {
|
if (!recipe.value || !recipe.value.slug) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newVersion = await api.recipes.updateImage(recipe.value.slug, fileObject);
|
const newVersion = await api.recipes.updateImage(recipe.value.slug, fileObject);
|
||||||
|
@ -656,8 +655,8 @@ export default defineComponent({
|
||||||
referenceId: uuid4(),
|
referenceId: uuid4(),
|
||||||
title: "",
|
title: "",
|
||||||
note: x,
|
note: x,
|
||||||
unit: null,
|
unit: undefined,
|
||||||
food: null,
|
food: undefined,
|
||||||
disableAmount: true,
|
disableAmount: true,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
};
|
};
|
||||||
|
@ -671,8 +670,8 @@ export default defineComponent({
|
||||||
referenceId: uuid4(),
|
referenceId: uuid4(),
|
||||||
title: "",
|
title: "",
|
||||||
note: "",
|
note: "",
|
||||||
unit: null,
|
unit: undefined,
|
||||||
food: null,
|
food: undefined,
|
||||||
disableAmount: true,
|
disableAmount: true,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
});
|
});
|
||||||
|
@ -762,7 +761,6 @@ export default defineComponent({
|
||||||
head: {},
|
head: {},
|
||||||
computed: {
|
computed: {
|
||||||
imageHeight() {
|
imageHeight() {
|
||||||
// @ts-ignore
|
|
||||||
return this.$vuetify.breakpoint.xs ? "200" : "400";
|
return this.$vuetify.breakpoint.xs ? "200" : "400";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -80,20 +80,21 @@
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
|
import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
import { until, invoke } from "@vueuse/core";
|
import { invoke, until } from "@vueuse/core";
|
||||||
import { Food, ParsedIngredient, Parser } from "~/api/class-interfaces/recipes/types";
|
import { ParsedIngredient, Parser } from "~/api/class-interfaces/recipes/types";
|
||||||
|
import { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit } from "~/types/api-types/recipe";
|
||||||
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
|
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { useRecipe, useFoods, useUnits } from "~/composables/recipes";
|
import { useFoods, useRecipe, useUnits } from "~/composables/recipes";
|
||||||
import { RecipeIngredientUnit } from "~/types/api-types/recipe";
|
|
||||||
interface Error {
|
interface Error {
|
||||||
ingredientIndex: number;
|
ingredientIndex: number;
|
||||||
unitError: Boolean;
|
unitError: boolean;
|
||||||
unitErrorMessage: string;
|
unitErrorMessage: string;
|
||||||
foodError: Boolean;
|
foodError: boolean;
|
||||||
foodErrorMessage: string;
|
foodErrorMessage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +126,10 @@ export default defineComponent({
|
||||||
const parsedIng = ref<ParsedIngredient[]>([]);
|
const parsedIng = ref<ParsedIngredient[]>([]);
|
||||||
|
|
||||||
async function fetchParsed() {
|
async function fetchParsed() {
|
||||||
if (!recipe.value) {
|
if (!recipe.value || !recipe.value.recipeIngredient) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const raw = recipe.value.recipeIngredient.map((ing) => ing.note);
|
const raw = recipe.value.recipeIngredient.map((ing) => ing.note ?? "");
|
||||||
const { data } = await api.recipes.parseIngredients(parser.value, raw);
|
const { data } = await api.recipes.parseIngredients(parser.value, raw);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -187,7 +188,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const errors = ref<Error[]>([]);
|
const errors = ref<Error[]>([]);
|
||||||
|
|
||||||
function checkForUnit(unit: RecipeIngredientUnit | null) {
|
function checkForUnit(unit?: IngredientUnit | CreateIngredientUnit) {
|
||||||
if (!unit) {
|
if (!unit) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -197,7 +198,7 @@ export default defineComponent({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForFood(food: Food | null) {
|
function checkForFood(food?: IngredientFood | CreateIngredientFood) {
|
||||||
if (!food) {
|
if (!food) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -207,7 +208,7 @@ export default defineComponent({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createFood(food: Food, index: number) {
|
async function createFood(food: CreateIngredientFood, index: number) {
|
||||||
workingFoodData.name = food.name;
|
workingFoodData.name = food.name;
|
||||||
await actions.createOne();
|
await actions.createOne();
|
||||||
errors.value[index].foodError = false;
|
errors.value[index].foodError = false;
|
||||||
|
@ -227,16 +228,14 @@ export default defineComponent({
|
||||||
return ing;
|
return ing;
|
||||||
}
|
}
|
||||||
// Get food from foods
|
// Get food from foods
|
||||||
const food = foods.value.find((f) => f.name === ing.food?.name);
|
ing.food = foods.value.find((f) => f.name === ing.food?.name);
|
||||||
ing.food = food || null;
|
|
||||||
|
|
||||||
// Get unit from units
|
// Get unit from units
|
||||||
const unit = units.value.find((u) => u.name === ing.unit?.name);
|
ing.unit = units.value.find((u) => u.name === ing.unit?.name);
|
||||||
ing.unit = unit || null;
|
|
||||||
return ing;
|
return ing;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!recipe.value) {
|
if (!recipe.value || !recipe.value.slug) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,4 +271,4 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -314,7 +314,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, useRouter, useContext } from "@nuxtjs/composition-api";
|
import {
|
||||||
|
defineComponent,
|
||||||
|
reactive,
|
||||||
|
toRefs,
|
||||||
|
ref,
|
||||||
|
useRouter,
|
||||||
|
useContext,
|
||||||
|
computed,
|
||||||
|
useRoute
|
||||||
|
} from "@nuxtjs/composition-api";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
// @ts-ignore No Types for v-jsoneditor
|
// @ts-ignore No Types for v-jsoneditor
|
||||||
import VJsoneditor from "v-jsoneditor";
|
import VJsoneditor from "v-jsoneditor";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
@ -322,6 +332,8 @@ import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategory
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import { Recipe } from "~/types/api-types/recipe";
|
import { Recipe } from "~/types/api-types/recipe";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
import { VForm} from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { VJsoneditor, RecipeCategoryTagSelector },
|
components: { VJsoneditor, RecipeCategoryTagSelector },
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -330,7 +342,6 @@ export default defineComponent({
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore - $globals not found in type definition
|
|
||||||
const { $globals } = useContext();
|
const { $globals } = useContext();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
|
@ -362,20 +373,39 @@ export default defineComponent({
|
||||||
];
|
];
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
function handleResponse(response: any, edit: Boolean = false) {
|
function handleResponse(response: AxiosResponse<string> | null, edit = false) {
|
||||||
if (response?.status !== 201) {
|
if (response?.status !== 201) {
|
||||||
state.error = true;
|
state.error = true;
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/recipe/${response.data}?edit=${edit}`);
|
router.push(`/recipe/${response.data}?edit=${edit.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tab = computed({
|
||||||
|
set(tab: string) {
|
||||||
|
router.replace({ query: { ...route.value.query, tab } });
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return route.value.query.tab as string;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const recipeUrl = computed({
|
||||||
|
set(recipe_import_url: string) {
|
||||||
|
recipe_import_url = recipe_import_url.trim()
|
||||||
|
router.replace({ query: { ...route.value.query, recipe_import_url } });
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return route.value.query.recipe_import_url as string;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// ===================================================
|
// ===================================================
|
||||||
// Recipe Debug URL Scraper
|
// Recipe Debug URL Scraper
|
||||||
// @ts-ignore
|
|
||||||
|
|
||||||
const debugTreeView = ref(false);
|
const debugTreeView = ref(false);
|
||||||
|
|
||||||
|
@ -425,6 +455,8 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { response } = await api.recipes.createOne({ name });
|
const { response } = await api.recipes.createOne({ name });
|
||||||
|
// TODO createOne claims to return a Recipe, but actually the API only returns a string
|
||||||
|
// @ts-ignore
|
||||||
handleResponse(response, true);
|
handleResponse(response, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,6 +499,8 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
tab,
|
||||||
|
recipeUrl,
|
||||||
bulkCreate,
|
bulkCreate,
|
||||||
bulkUrls,
|
bulkUrls,
|
||||||
lockBulkImport,
|
lockBulkImport,
|
||||||
|
@ -490,30 +524,6 @@ export default defineComponent({
|
||||||
title: this.$t("general.create") as string,
|
title: this.$t("general.create") as string,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
// Computed State is used because of the limitation of vue-composition-api in v2.0
|
|
||||||
computed: {
|
|
||||||
tab: {
|
|
||||||
set(tab) {
|
|
||||||
// @ts-ignore
|
|
||||||
this.$router.replace({ query: { ...this.$route.query, tab } });
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
// @ts-ignore
|
|
||||||
return this.$route.query.tab;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
recipeUrl: {
|
|
||||||
set(recipe_import_url) {
|
|
||||||
// @ts-ignore
|
|
||||||
recipe_import_url = recipe_import_url.trim()
|
|
||||||
this.$router.replace({ query: { ...this.$route.query, recipe_import_url } });
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
// @ts-ignore
|
|
||||||
return this.$route.query.recipe_import_url;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,7 @@ import { validators } from "@/composables/use-validators";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
import { useRouterQuery } from "@/composables/use-router";
|
import { useRouterQuery } from "@/composables/use-router";
|
||||||
|
import { VForm} from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
layout: "basic",
|
layout: "basic",
|
||||||
|
@ -179,4 +180,4 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -325,11 +325,11 @@ export default defineComponent({
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data && data !== undefined) {
|
if (data && data !== undefined) {
|
||||||
console.log("Computed Meta. RefKey=");
|
console.log("Computed Meta. RefKey=");
|
||||||
const imageURL = recipeImage(data.slug);
|
const imageURL = data.slug ? recipeImage(data.slug) : undefined;
|
||||||
title.value = data.name;
|
title.value = data.name;
|
||||||
|
|
||||||
meta.value = [
|
meta.value = [
|
||||||
{ hid: "og:title", property: "og:title", content: data.name },
|
{ hid: "og:title", property: "og:title", content: data.name ?? "" },
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
{
|
{
|
||||||
hid: "og:desc",
|
hid: "og:desc",
|
||||||
|
@ -339,7 +339,7 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
hid: "og-image",
|
hid: "og-image",
|
||||||
property: "og:image",
|
property: "og:image",
|
||||||
content: imageURL,
|
content: imageURL ?? "",
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
{
|
{
|
||||||
|
@ -360,7 +360,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const { $vuetify } = useContext();
|
const { $vuetify } = useContext();
|
||||||
|
|
||||||
const enableLandscape = computed(() => {
|
const enableLandscape = computed(() => {
|
||||||
|
@ -400,9 +399,8 @@ export default defineComponent({
|
||||||
head: {},
|
head: {},
|
||||||
computed: {
|
computed: {
|
||||||
imageHeight() {
|
imageHeight() {
|
||||||
// @ts-ignore
|
|
||||||
return this.$vuetify.breakpoint.xs ? "200" : "400";
|
return this.$vuetify.breakpoint.xs ? "200" : "400";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -79,7 +79,6 @@ const MIGRATIONS = {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
// @ts-ignore
|
|
||||||
const { $globals } = useContext();
|
const { $globals } = useContext();
|
||||||
|
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
@ -303,4 +302,4 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -152,7 +152,7 @@
|
||||||
</section>
|
</section>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, ref, useContext, onMounted } from "@nuxtjs/composition-api";
|
import { defineComponent, reactive, ref, useContext, onMounted } from "@nuxtjs/composition-api";
|
||||||
import RecipeDataTable from "~/components/Domain/Recipe/RecipeDataTable.vue";
|
import RecipeDataTable from "~/components/Domain/Recipe/RecipeDataTable.vue";
|
||||||
|
@ -176,7 +176,6 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const { getAllRecipes, refreshRecipes } = useRecipes(true, true);
|
const { getAllRecipes, refreshRecipes } = useRecipes(true, true);
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const { $globals } = useContext();
|
const { $globals } = useContext();
|
||||||
|
|
||||||
const selected = ref<Recipe[]>([]);
|
const selected = ref<Recipe[]>([]);
|
||||||
|
@ -272,7 +271,7 @@ export default defineComponent({
|
||||||
async function exportSelected() {
|
async function exportSelected() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await api.bulk.bulkExport({
|
const { data } = await api.bulk.bulkExport({
|
||||||
recipes: selected.value.map((x: Recipe) => x.slug),
|
recipes: selected.value.map((x: Recipe) => x.slug ?? ""),
|
||||||
exportType: "json",
|
exportType: "json",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -289,7 +288,7 @@ export default defineComponent({
|
||||||
async function tagSelected() {
|
async function tagSelected() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const recipes = selected.value.map((x: Recipe) => x.slug);
|
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||||
await api.bulk.bulkTag({ recipes, tags: toSetTags.value });
|
await api.bulk.bulkTag({ recipes, tags: toSetTags.value });
|
||||||
await refreshRecipes();
|
await refreshRecipes();
|
||||||
resetAll();
|
resetAll();
|
||||||
|
@ -300,7 +299,7 @@ export default defineComponent({
|
||||||
async function categorizeSelected() {
|
async function categorizeSelected() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const recipes = selected.value.map((x: Recipe) => x.slug);
|
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||||
await api.bulk.bulkCategorize({ recipes, categories: toSetCategories.value });
|
await api.bulk.bulkCategorize({ recipes, categories: toSetCategories.value });
|
||||||
await refreshRecipes();
|
await refreshRecipes();
|
||||||
resetAll();
|
resetAll();
|
||||||
|
@ -309,7 +308,7 @@ export default defineComponent({
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const recipes = selected.value.map((x: Recipe) => x.slug);
|
const recipes = selected.value.map((x: Recipe) => x.slug ?? "");
|
||||||
|
|
||||||
const { response, data } = await api.bulk.bulkDelete({ recipes });
|
const { response, data } = await api.bulk.bulkDelete({ recipes });
|
||||||
|
|
||||||
|
@ -327,6 +326,7 @@ export default defineComponent({
|
||||||
title: "Tag Recipes",
|
title: "Tag Recipes",
|
||||||
mode: MODES.tag,
|
mode: MODES.tag,
|
||||||
tag: "",
|
tag: "",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
callback: () => {},
|
callback: () => {},
|
||||||
icon: $globals.icons.tags,
|
icon: $globals.icons.tags,
|
||||||
});
|
});
|
||||||
|
@ -389,4 +389,4 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { defineComponent, useRoute, reactive, toRefs, onMounted } from "@nuxtjs/composition-api";
|
import { defineComponent, useRoute, reactive, toRefs, onMounted } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
|
||||||
|
@ -73,4 +73,4 @@ export default defineComponent({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -62,10 +62,11 @@
|
||||||
</section>
|
</section>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, useContext, ref } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext, ref } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -125,4 +126,4 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -110,11 +110,12 @@
|
||||||
</section>
|
</section>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, reactive, defineComponent, computed, useContext, watch } from "@nuxtjs/composition-api";
|
import { ref, reactive, defineComponent, computed, useContext, watch } from "@nuxtjs/composition-api";
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
import UserAvatar from "~/components/Domain/User/UserAvatar.vue";
|
||||||
|
import { VForm } from "~/types/vuetify";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
@ -179,4 +180,4 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
|
import { Plugin } from "@nuxt/types"
|
||||||
import { useDark } from "@vueuse/core";
|
import { useDark } from "@vueuse/core";
|
||||||
|
|
||||||
export default ({ $vuetify }: any) => {
|
const darkModePlugin: Plugin = ({ $vuetify }, _) => {
|
||||||
const isDark = useDark();
|
const isDark = useDark();
|
||||||
|
|
||||||
if (isDark.value) {
|
$vuetify.theme.dark = isDark.value;
|
||||||
$vuetify.theme.dark = true;
|
|
||||||
} else {
|
|
||||||
$vuetify.theme.dark = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default darkModePlugin;
|
||||||
|
|
|
@ -1,7 +1,27 @@
|
||||||
|
import { Plugin } from "@nuxt/types"
|
||||||
import { icons } from "~/utils/icons";
|
import { icons } from "~/utils/icons";
|
||||||
|
import { Icon } from "~/utils/icons/icon-type";
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty-pattern
|
interface Globals {
|
||||||
export default ({}, inject: any) => {
|
icons: Icon;
|
||||||
// Inject $hello(msg) in Vue, context and store.
|
}
|
||||||
inject("globals", { icons });
|
|
||||||
|
declare module "vue/types/vue" {
|
||||||
|
interface Vue {
|
||||||
|
$globals: Globals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@nuxt/types" {
|
||||||
|
interface Context {
|
||||||
|
$globals: Globals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalsPlugin: Plugin = (_, inject) => {
|
||||||
|
inject("globals", {
|
||||||
|
icons
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default globalsPlugin
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
export default ({ $vuetify, $config }: any) => {
|
import { Plugin } from "@nuxt/types"
|
||||||
$vuetify.theme.themes = $config.themes;
|
|
||||||
|
const themePlugin: Plugin = ({ $vuetify, $config }) => {
|
||||||
|
$vuetify.theme.themes = $config.themes as typeof $vuetify.theme.themes
|
||||||
|
|
||||||
if ($config.useDark) {
|
if ($config.useDark) {
|
||||||
$vuetify.theme.dark = true;
|
$vuetify.theme.dark = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default themePlugin;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { Plugin } from "@nuxt/types"
|
||||||
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
||||||
import { alert } from "~/composables/use-toast";
|
import { alert } from "~/composables/use-toast";
|
||||||
|
|
||||||
export default function ({ $axios }: { $axios: NuxtAxiosInstance }) {
|
const toastPlugin: Plugin = ({ $axios }: { $axios: NuxtAxiosInstance }) => {
|
||||||
$axios.onResponse((response) => {
|
$axios.onResponse((response) => {
|
||||||
if (response.data.message) {
|
if (response.data.message) {
|
||||||
alert.info(response.data.message);
|
alert.info(response.data.message);
|
||||||
|
@ -13,3 +14,5 @@ export default function ({ $axios }: { $axios: NuxtAxiosInstance }) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default toastPlugin;
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
"~/*": ["./*"],
|
"~/*": ["./*"],
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
},
|
},
|
||||||
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node", "@nuxtjs/i18n", "@nuxtjs/auth-next", "@types/sortablejs"]
|
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node", "@nuxtjs/i18n", "@nuxtjs/auth-next", "@nuxtjs/vuetify", "@types/sortablejs"]
|
||||||
},
|
},
|
||||||
|
"include": ["**/*", ".eslintrc.js"],
|
||||||
"exclude": ["node_modules", ".nuxt", "dist"],
|
"exclude": ["node_modules", ".nuxt", "dist"],
|
||||||
"vueCompilerOptions": {
|
"vueCompilerOptions": {
|
||||||
"experimentalCompatMode": 2
|
"experimentalCompatMode": 2
|
||||||
|
|
|
@ -5,6 +5,26 @@
|
||||||
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export interface AdminAboutInfo {
|
||||||
|
production: boolean;
|
||||||
|
version: string;
|
||||||
|
demoStatus: boolean;
|
||||||
|
versionLatest: string;
|
||||||
|
apiPort: number;
|
||||||
|
apiDocs: boolean;
|
||||||
|
dbType: string;
|
||||||
|
dbUrl: string;
|
||||||
|
defaultGroup: string;
|
||||||
|
}
|
||||||
|
export interface AllBackups {
|
||||||
|
imports: BackupFile[];
|
||||||
|
templates: string[];
|
||||||
|
}
|
||||||
|
export interface BackupFile {
|
||||||
|
name: string;
|
||||||
|
date: string;
|
||||||
|
size: string;
|
||||||
|
}
|
||||||
export interface AppInfo {
|
export interface AppInfo {
|
||||||
production: boolean;
|
production: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
|
@ -17,36 +37,31 @@ export interface AppStatistics {
|
||||||
uncategorizedRecipes: number;
|
uncategorizedRecipes: number;
|
||||||
untaggedRecipes: number;
|
untaggedRecipes: number;
|
||||||
}
|
}
|
||||||
export interface BackupJob {
|
|
||||||
tag?: string;
|
|
||||||
options: BackupOptions;
|
|
||||||
templates?: string[];
|
|
||||||
}
|
|
||||||
export interface BackupOptions {
|
export interface BackupOptions {
|
||||||
recipes?: boolean;
|
recipes?: boolean;
|
||||||
settings?: boolean;
|
settings?: boolean;
|
||||||
pages?: boolean;
|
|
||||||
themes?: boolean;
|
themes?: boolean;
|
||||||
groups?: boolean;
|
groups?: boolean;
|
||||||
users?: boolean;
|
users?: boolean;
|
||||||
notifications?: boolean;
|
notifications?: boolean;
|
||||||
}
|
}
|
||||||
export interface CategoryBase {
|
export interface CheckAppConfig {
|
||||||
name: string;
|
emailReady?: boolean;
|
||||||
id: number;
|
ldapReady?: boolean;
|
||||||
slug: string;
|
baseUrlSet?: boolean;
|
||||||
}
|
}
|
||||||
export interface ChowdownURL {
|
export interface ChowdownURL {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
export interface Colors {
|
export interface CommentImport {
|
||||||
primary?: string;
|
name: string;
|
||||||
accent?: string;
|
status: boolean;
|
||||||
secondary?: string;
|
exception?: string;
|
||||||
success?: string;
|
}
|
||||||
info?: string;
|
export interface CreateBackup {
|
||||||
warning?: string;
|
tag?: string;
|
||||||
error?: string;
|
options: BackupOptions;
|
||||||
|
templates?: string[];
|
||||||
}
|
}
|
||||||
export interface CustomPageBase {
|
export interface CustomPageBase {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -62,51 +77,87 @@ export interface RecipeCategoryResponse {
|
||||||
}
|
}
|
||||||
export interface Recipe {
|
export interface Recipe {
|
||||||
id?: number;
|
id?: number;
|
||||||
name: string;
|
userId?: string;
|
||||||
slug: string;
|
groupId?: string;
|
||||||
|
name?: string;
|
||||||
|
slug?: string;
|
||||||
image?: unknown;
|
image?: unknown;
|
||||||
description?: string;
|
|
||||||
recipeCategory?: string[];
|
|
||||||
tags?: string[];
|
|
||||||
rating?: number;
|
|
||||||
dateAdded?: string;
|
|
||||||
dateUpdated?: string;
|
|
||||||
recipeYield?: string;
|
recipeYield?: string;
|
||||||
recipeIngredient?: RecipeIngredient[];
|
|
||||||
recipeInstructions?: RecipeStep[];
|
|
||||||
nutrition?: Nutrition;
|
|
||||||
tools?: string[];
|
|
||||||
totalTime?: string;
|
totalTime?: string;
|
||||||
prepTime?: string;
|
prepTime?: string;
|
||||||
|
cookTime?: string;
|
||||||
performTime?: string;
|
performTime?: string;
|
||||||
|
description?: string;
|
||||||
|
recipeCategory?: RecipeTag[];
|
||||||
|
tags?: RecipeTag[];
|
||||||
|
tools?: RecipeTool[];
|
||||||
|
rating?: number;
|
||||||
|
orgURL?: string;
|
||||||
|
recipeIngredient?: RecipeIngredient[];
|
||||||
|
dateAdded?: string;
|
||||||
|
dateUpdated?: string;
|
||||||
|
recipeInstructions?: RecipeStep[];
|
||||||
|
nutrition?: Nutrition;
|
||||||
settings?: RecipeSettings;
|
settings?: RecipeSettings;
|
||||||
assets?: RecipeAsset[];
|
assets?: RecipeAsset[];
|
||||||
notes?: RecipeNote[];
|
notes?: RecipeNote[];
|
||||||
orgURL?: string;
|
|
||||||
extras?: {
|
extras?: {
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
comments?: CommentOut[];
|
comments?: RecipeCommentOut[];
|
||||||
|
}
|
||||||
|
export interface RecipeTag {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
export interface RecipeTool {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
id?: number;
|
||||||
|
onHand?: boolean;
|
||||||
}
|
}
|
||||||
export interface RecipeIngredient {
|
export interface RecipeIngredient {
|
||||||
title?: string;
|
title?: string;
|
||||||
note?: string;
|
note?: string;
|
||||||
unit?: RecipeIngredientUnit;
|
unit?: IngredientUnit | CreateIngredientUnit;
|
||||||
food?: RecipeIngredientFood;
|
food?: IngredientFood | CreateIngredientFood;
|
||||||
disableAmount?: boolean;
|
disableAmount?: boolean;
|
||||||
quantity?: number;
|
quantity?: number;
|
||||||
|
referenceId?: string;
|
||||||
}
|
}
|
||||||
export interface RecipeIngredientUnit {
|
export interface IngredientUnit {
|
||||||
name?: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
fraction?: boolean;
|
||||||
|
abbreviation?: string;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
export interface RecipeIngredientFood {
|
export interface CreateIngredientUnit {
|
||||||
name?: string;
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
fraction?: boolean;
|
||||||
|
abbreviation?: string;
|
||||||
|
}
|
||||||
|
export interface IngredientFood {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface CreateIngredientFood {
|
||||||
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
export interface RecipeStep {
|
export interface RecipeStep {
|
||||||
|
id?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
text: string;
|
text: string;
|
||||||
|
ingredientReferences?: IngredientReferences[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A list of ingredient references.
|
||||||
|
*/
|
||||||
|
export interface IngredientReferences {
|
||||||
|
referenceId?: string;
|
||||||
}
|
}
|
||||||
export interface Nutrition {
|
export interface Nutrition {
|
||||||
calories?: string;
|
calories?: string;
|
||||||
|
@ -124,6 +175,7 @@ export interface RecipeSettings {
|
||||||
landscapeView?: boolean;
|
landscapeView?: boolean;
|
||||||
disableComments?: boolean;
|
disableComments?: boolean;
|
||||||
disableAmount?: boolean;
|
disableAmount?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
}
|
}
|
||||||
export interface RecipeAsset {
|
export interface RecipeAsset {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -134,12 +186,13 @@ export interface RecipeNote {
|
||||||
title: string;
|
title: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
export interface CommentOut {
|
export interface RecipeCommentOut {
|
||||||
|
recipeId: number;
|
||||||
text: string;
|
text: string;
|
||||||
id: number;
|
id: string;
|
||||||
uuid: string;
|
createdAt: string;
|
||||||
recipeSlug: string;
|
updateAt: string;
|
||||||
dateAdded: string;
|
userId: string;
|
||||||
user: UserBase;
|
user: UserBase;
|
||||||
}
|
}
|
||||||
export interface UserBase {
|
export interface UserBase {
|
||||||
|
@ -159,16 +212,6 @@ export interface CustomPageOut {
|
||||||
categories?: RecipeCategoryResponse[];
|
categories?: RecipeCategoryResponse[];
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
export interface DebugInfo {
|
|
||||||
production: boolean;
|
|
||||||
version: string;
|
|
||||||
demoStatus: boolean;
|
|
||||||
apiPort: number;
|
|
||||||
apiDocs: boolean;
|
|
||||||
dbType: string;
|
|
||||||
dbUrl: string;
|
|
||||||
defaultGroup: string;
|
|
||||||
}
|
|
||||||
export interface GroupImport {
|
export interface GroupImport {
|
||||||
name: string;
|
name: string;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
|
@ -182,7 +225,6 @@ export interface ImportBase {
|
||||||
export interface ImportJob {
|
export interface ImportJob {
|
||||||
recipes?: boolean;
|
recipes?: boolean;
|
||||||
settings?: boolean;
|
settings?: boolean;
|
||||||
pages?: boolean;
|
|
||||||
themes?: boolean;
|
themes?: boolean;
|
||||||
groups?: boolean;
|
groups?: boolean;
|
||||||
users?: boolean;
|
users?: boolean;
|
||||||
|
@ -191,14 +233,6 @@ export interface ImportJob {
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
rebase?: boolean;
|
rebase?: boolean;
|
||||||
}
|
}
|
||||||
export interface Imports {
|
|
||||||
imports: LocalBackup[];
|
|
||||||
templates: string[];
|
|
||||||
}
|
|
||||||
export interface LocalBackup {
|
|
||||||
name: string;
|
|
||||||
date: string;
|
|
||||||
}
|
|
||||||
export interface MigrationFile {
|
export interface MigrationFile {
|
||||||
name: string;
|
name: string;
|
||||||
date: string;
|
date: string;
|
||||||
|
@ -229,23 +263,6 @@ export interface SettingsImport {
|
||||||
status: boolean;
|
status: boolean;
|
||||||
exception?: string;
|
exception?: string;
|
||||||
}
|
}
|
||||||
export interface SiteSettings {
|
|
||||||
language?: string;
|
|
||||||
firstDayOfWeek?: number;
|
|
||||||
showRecent?: boolean;
|
|
||||||
cardsPerSection?: number;
|
|
||||||
categories?: CategoryBase[];
|
|
||||||
}
|
|
||||||
export interface SiteTheme {
|
|
||||||
id?: number;
|
|
||||||
name?: string;
|
|
||||||
colors?: Colors;
|
|
||||||
}
|
|
||||||
export interface ThemeImport {
|
|
||||||
name: string;
|
|
||||||
status: boolean;
|
|
||||||
exception?: string;
|
|
||||||
}
|
|
||||||
export interface UserImport {
|
export interface UserImport {
|
||||||
name: string;
|
name: string;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
|
|
184
frontend/types/api-types/cookbook.ts
Normal file
184
frontend/types/api-types/cookbook.ts
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
/* This file was automatically generated from pydantic models by running pydantic2ts.
|
||||||
|
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CategoryBase {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
export interface CreateCookBook {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
slug?: string;
|
||||||
|
position?: number;
|
||||||
|
categories?: CategoryBase[];
|
||||||
|
}
|
||||||
|
export interface ReadCookBook {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
slug?: string;
|
||||||
|
position?: number;
|
||||||
|
categories?: CategoryBase[];
|
||||||
|
id: number;
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
|
export interface RecipeCategoryResponse {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
slug: string;
|
||||||
|
recipes?: Recipe[];
|
||||||
|
}
|
||||||
|
export interface Recipe {
|
||||||
|
id?: number;
|
||||||
|
userId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
name?: string;
|
||||||
|
slug?: string;
|
||||||
|
image?: unknown;
|
||||||
|
recipeYield?: string;
|
||||||
|
totalTime?: string;
|
||||||
|
prepTime?: string;
|
||||||
|
cookTime?: string;
|
||||||
|
performTime?: string;
|
||||||
|
description?: string;
|
||||||
|
recipeCategory?: RecipeTag[];
|
||||||
|
tags?: RecipeTag[];
|
||||||
|
tools?: RecipeTool[];
|
||||||
|
rating?: number;
|
||||||
|
orgURL?: string;
|
||||||
|
recipeIngredient?: RecipeIngredient[];
|
||||||
|
dateAdded?: string;
|
||||||
|
dateUpdated?: string;
|
||||||
|
recipeInstructions?: RecipeStep[];
|
||||||
|
nutrition?: Nutrition;
|
||||||
|
settings?: RecipeSettings;
|
||||||
|
assets?: RecipeAsset[];
|
||||||
|
notes?: RecipeNote[];
|
||||||
|
extras?: {
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
|
comments?: RecipeCommentOut[];
|
||||||
|
}
|
||||||
|
export interface RecipeTag {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
export interface RecipeTool {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
id?: number;
|
||||||
|
onHand?: boolean;
|
||||||
|
}
|
||||||
|
export interface RecipeIngredient {
|
||||||
|
title?: string;
|
||||||
|
note?: string;
|
||||||
|
unit?: IngredientUnit | CreateIngredientUnit;
|
||||||
|
food?: IngredientFood | CreateIngredientFood;
|
||||||
|
disableAmount?: boolean;
|
||||||
|
quantity?: number;
|
||||||
|
referenceId?: string;
|
||||||
|
}
|
||||||
|
export interface IngredientUnit {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
fraction?: boolean;
|
||||||
|
abbreviation?: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface CreateIngredientUnit {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
fraction?: boolean;
|
||||||
|
abbreviation?: string;
|
||||||
|
}
|
||||||
|
export interface IngredientFood {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface CreateIngredientFood {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
export interface RecipeStep {
|
||||||
|
id?: string;
|
||||||
|
title?: string;
|
||||||
|
text: string;
|
||||||
|
ingredientReferences?: IngredientReferences[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A list of ingredient references.
|
||||||
|
*/
|
||||||
|
export interface IngredientReferences {
|
||||||
|
referenceId?: string;
|
||||||
|
}
|
||||||
|
export interface Nutrition {
|
||||||
|
calories?: string;
|
||||||
|
fatContent?: string;
|
||||||
|
proteinContent?: string;
|
||||||
|
carbohydrateContent?: string;
|
||||||
|
fiberContent?: string;
|
||||||
|
sodiumContent?: string;
|
||||||
|
sugarContent?: string;
|
||||||
|
}
|
||||||
|
export interface RecipeSettings {
|
||||||
|
public?: boolean;
|
||||||
|
showNutrition?: boolean;
|
||||||
|
showAssets?: boolean;
|
||||||
|
landscapeView?: boolean;
|
||||||
|
disableComments?: boolean;
|
||||||
|
disableAmount?: boolean;
|
||||||
|
locked?: boolean;
|
||||||
|
}
|
||||||
|
export interface RecipeAsset {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
fileName?: string;
|
||||||
|
}
|
||||||
|
export interface RecipeNote {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
export interface RecipeCommentOut {
|
||||||
|
recipeId: number;
|
||||||
|
text: string;
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updateAt: string;
|
||||||
|
userId: string;
|
||||||
|
user: UserBase;
|
||||||
|
}
|
||||||
|
export interface UserBase {
|
||||||
|
id: number;
|
||||||
|
username?: string;
|
||||||
|
admin: boolean;
|
||||||
|
}
|
||||||
|
export interface RecipeCookBook {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
slug?: string;
|
||||||
|
position?: number;
|
||||||
|
categories: RecipeCategoryResponse[];
|
||||||
|
id: number;
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
|
export interface SaveCookBook {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
slug?: string;
|
||||||
|
position?: number;
|
||||||
|
categories?: CategoryBase[];
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
|
export interface UpdateCookBook {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
slug?: string;
|
||||||
|
position?: number;
|
||||||
|
categories?: CategoryBase[];
|
||||||
|
id: number;
|
||||||
|
}
|
28
frontend/types/api-types/group.ts
Normal file
28
frontend/types/api-types/group.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
/* This file was automatically generated from pydantic models by running pydantic2ts.
|
||||||
|
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CreateWebhook {
|
||||||
|
enabled?: boolean;
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
time?: string;
|
||||||
|
}
|
||||||
|
export interface ReadWebhook {
|
||||||
|
enabled?: boolean;
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
time?: string;
|
||||||
|
groupId: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface SaveWebhook {
|
||||||
|
enabled?: boolean;
|
||||||
|
name?: string;
|
||||||
|
url?: string;
|
||||||
|
time?: string;
|
||||||
|
groupId: string;
|
||||||
|
}
|
|
@ -5,6 +5,15 @@
|
||||||
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "snack";
|
||||||
|
|
||||||
|
export interface CreatePlanEntry {
|
||||||
|
date: string;
|
||||||
|
entryType?: PlanEntryType & string;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
recipeId?: number;
|
||||||
|
}
|
||||||
export interface ListItem {
|
export interface ListItem {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
|
@ -36,9 +45,90 @@ export interface MealPlanOut {
|
||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
planDays: MealDayIn[];
|
planDays: MealDayIn[];
|
||||||
uid: number;
|
id: number;
|
||||||
shoppingList?: number;
|
shoppingList?: number;
|
||||||
}
|
}
|
||||||
|
export interface ReadPlanEntry {
|
||||||
|
date: string;
|
||||||
|
entryType?: PlanEntryType & string;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
recipeId?: number;
|
||||||
|
id: number;
|
||||||
|
groupId: string;
|
||||||
|
recipe?: RecipeSummary;
|
||||||
|
}
|
||||||
|
export interface RecipeSummary {
|
||||||
|
id?: number;
|
||||||
|
userId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
name?: string;
|
||||||
|
slug?: string;
|
||||||
|
image?: unknown;
|
||||||
|
recipeYield?: string;
|
||||||
|
totalTime?: string;
|
||||||
|
prepTime?: string;
|
||||||
|
cookTime?: string;
|
||||||
|
performTime?: string;
|
||||||
|
description?: string;
|
||||||
|
recipeCategory?: RecipeTag[];
|
||||||
|
tags?: RecipeTag[];
|
||||||
|
tools?: RecipeTool[];
|
||||||
|
rating?: number;
|
||||||
|
orgURL?: string;
|
||||||
|
recipeIngredient?: RecipeIngredient[];
|
||||||
|
dateAdded?: string;
|
||||||
|
dateUpdated?: string;
|
||||||
|
}
|
||||||
|
export interface RecipeTag {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
export interface RecipeTool {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
id?: number;
|
||||||
|
onHand?: boolean;
|
||||||
|
}
|
||||||
|
export interface RecipeIngredient {
|
||||||
|
title?: string;
|
||||||
|
note?: string;
|
||||||
|
unit?: IngredientUnit | CreateIngredientUnit;
|
||||||
|
food?: IngredientFood | CreateIngredientFood;
|
||||||
|
disableAmount?: boolean;
|
||||||
|
quantity?: number;
|
||||||
|
referenceId?: string;
|
||||||
|
}
|
||||||
|
export interface IngredientUnit {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
fraction?: boolean;
|
||||||
|
abbreviation?: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface CreateIngredientUnit {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
fraction?: boolean;
|
||||||
|
abbreviation?: string;
|
||||||
|
}
|
||||||
|
export interface IngredientFood {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface CreateIngredientFood {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
export interface SavePlanEntry {
|
||||||
|
date: string;
|
||||||
|
entryType?: PlanEntryType & string;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
recipeId?: number;
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
export interface ShoppingListIn {
|
export interface ShoppingListIn {
|
||||||
name: string;
|
name: string;
|
||||||
group?: string;
|
group?: string;
|
||||||
|
@ -50,3 +140,12 @@ export interface ShoppingListOut {
|
||||||
items: ListItem[];
|
items: ListItem[];
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
export interface UpdatePlanEntry {
|
||||||
|
date: string;
|
||||||
|
entryType?: PlanEntryType & string;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
recipeId?: number;
|
||||||
|
id: number;
|
||||||
|
groupId: string;
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue