mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-24 07:39:41 +02:00
feat: Timeline Image Uploader Improvements (#2494)
* improved UI responsiveness and added image preview * added global image cropper component * added image cropper to last made dialog * style tweaks * added more specific text for creating event * mopped up some slop * renamed height and width vars --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
parent
e24e28ae03
commit
2151451634
9 changed files with 246 additions and 8 deletions
|
@ -5,7 +5,7 @@
|
||||||
v-model="madeThisDialog"
|
v-model="madeThisDialog"
|
||||||
:icon="$globals.icons.chefHat"
|
:icon="$globals.icons.chefHat"
|
||||||
:title="$tc('recipe.made-this')"
|
:title="$tc('recipe.made-this')"
|
||||||
:submit-text="$tc('general.save')"
|
:submit-text="$tc('recipe.add-to-timeline')"
|
||||||
@submit="createTimelineEvent"
|
@submit="createTimelineEvent"
|
||||||
>
|
>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
|
@ -49,6 +49,7 @@
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-col cols="auto" align-self="center">
|
<v-col cols="auto" align-self="center">
|
||||||
<AppButtonUpload
|
<AppButtonUpload
|
||||||
|
v-if="!newTimelineEventImage"
|
||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
url="none"
|
url="none"
|
||||||
file-name="image"
|
file-name="image"
|
||||||
|
@ -58,6 +59,24 @@
|
||||||
:post="false"
|
:post="false"
|
||||||
@uploaded="uploadImage"
|
@uploaded="uploadImage"
|
||||||
/>
|
/>
|
||||||
|
<v-btn
|
||||||
|
v-if="!!newTimelineEventImage"
|
||||||
|
color="error"
|
||||||
|
@click="clearImage"
|
||||||
|
>
|
||||||
|
<v-icon left>{{ $globals.icons.close }}</v-icon>
|
||||||
|
{{ $i18n.tc('recipe.remove-image') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="newTimelineEventImage && newTimelineEventImagePreviewUrl">
|
||||||
|
<v-col cols="12" align-self="center">
|
||||||
|
<ImageCropper
|
||||||
|
:img="newTimelineEventImagePreviewUrl"
|
||||||
|
cropper-height="20vh"
|
||||||
|
cropper-width="100%"
|
||||||
|
@save="updateUploadedImage"
|
||||||
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
@ -120,7 +139,9 @@ export default defineComponent({
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
recipeId: props.recipe?.id || "",
|
recipeId: props.recipe?.id || "",
|
||||||
});
|
});
|
||||||
const newTimelineEventImage = ref<File>();
|
const newTimelineEventImage = ref<Blob | File>();
|
||||||
|
const newTimelineEventImageName = ref<string>("");
|
||||||
|
const newTimelineEventImagePreviewUrl = ref<string>();
|
||||||
const newTimelineEventTimestamp = ref<string>();
|
const newTimelineEventTimestamp = ref<string>();
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
|
@ -133,8 +154,21 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function clearImage() {
|
||||||
|
newTimelineEventImage.value = undefined;
|
||||||
|
newTimelineEventImageName.value = "";
|
||||||
|
newTimelineEventImagePreviewUrl.value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function uploadImage(fileObject: File) {
|
function uploadImage(fileObject: File) {
|
||||||
newTimelineEventImage.value = fileObject;
|
newTimelineEventImage.value = fileObject;
|
||||||
|
newTimelineEventImageName.value = fileObject.name;
|
||||||
|
newTimelineEventImagePreviewUrl.value = URL.createObjectURL(fileObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUploadedImage(fileObject: Blob) {
|
||||||
|
newTimelineEventImage.value = fileObject;
|
||||||
|
newTimelineEventImagePreviewUrl.value = URL.createObjectURL(fileObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = reactive({datePickerMenu: false});
|
const state = reactive({datePickerMenu: false});
|
||||||
|
@ -166,7 +200,11 @@ export default defineComponent({
|
||||||
|
|
||||||
// update the image, if provided
|
// update the image, if provided
|
||||||
if (newTimelineEventImage.value && newEvent) {
|
if (newTimelineEventImage.value && newEvent) {
|
||||||
const imageResponse = await userApi.recipes.updateTimelineEventImage(newEvent.id, newTimelineEventImage.value);
|
const imageResponse = await userApi.recipes.updateTimelineEventImage(
|
||||||
|
newEvent.id,
|
||||||
|
newTimelineEventImage.value,
|
||||||
|
newTimelineEventImageName.value,
|
||||||
|
);
|
||||||
if (imageResponse.data) {
|
if (imageResponse.data) {
|
||||||
// @ts-ignore the image response data will always match a value of TimelineEventImage
|
// @ts-ignore the image response data will always match a value of TimelineEventImage
|
||||||
newEvent.image = imageResponse.data.image;
|
newEvent.image = imageResponse.data.image;
|
||||||
|
@ -176,7 +214,7 @@ export default defineComponent({
|
||||||
// reset form
|
// reset form
|
||||||
newTimelineEvent.value.eventMessage = "";
|
newTimelineEvent.value.eventMessage = "";
|
||||||
newTimelineEvent.value.timestamp = undefined;
|
newTimelineEvent.value.timestamp = undefined;
|
||||||
newTimelineEventImage.value = undefined;
|
clearImage();
|
||||||
madeThisDialog.value = false;
|
madeThisDialog.value = false;
|
||||||
domMadeThisForm.value?.reset();
|
domMadeThisForm.value?.reset();
|
||||||
|
|
||||||
|
@ -189,9 +227,12 @@ export default defineComponent({
|
||||||
madeThisDialog,
|
madeThisDialog,
|
||||||
newTimelineEvent,
|
newTimelineEvent,
|
||||||
newTimelineEventImage,
|
newTimelineEventImage,
|
||||||
|
newTimelineEventImagePreviewUrl,
|
||||||
newTimelineEventTimestamp,
|
newTimelineEventTimestamp,
|
||||||
createTimelineEvent,
|
createTimelineEvent,
|
||||||
|
clearImage,
|
||||||
uploadImage,
|
uploadImage,
|
||||||
|
updateUploadedImage,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<v-form ref="file">
|
<v-form ref="file">
|
||||||
<input ref="uploader" class="d-none" type="file" :accept="accept" @change="onFileChanged" />
|
<input ref="uploader" class="d-none" type="file" :accept="accept" @change="onFileChanged" />
|
||||||
<slot v-bind="{ isSelecting, onButtonClick }">
|
<slot v-bind="{ isSelecting, onButtonClick }">
|
||||||
<v-btn :loading="isSelecting" :small="small" color="info" :text="textBtn" @click="onButtonClick">
|
<v-btn :loading="isSelecting" :small="small" :color="color" :text="textBtn" :disabled="disabled" @click="onButtonClick">
|
||||||
<v-icon left> {{ effIcon }}</v-icon>
|
<v-icon left> {{ effIcon }}</v-icon>
|
||||||
{{ text ? text : defaultText }}
|
{{ text ? text : defaultText }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
@ -50,6 +50,14 @@ export default defineComponent({
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: "info",
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const file = ref<File | null>(null);
|
const file = ref<File | null>(null);
|
||||||
|
|
152
frontend/components/global/ImageCropper.vue
Normal file
152
frontend/components/global/ImageCropper.vue
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
<template>
|
||||||
|
<v-container class="pa-0">
|
||||||
|
<v-row no-gutters>
|
||||||
|
<v-col cols="2" align-self="center">
|
||||||
|
<v-container class="pa-0 mx-0">
|
||||||
|
<v-row v-for="(row, keyRow) in controls" :key="keyRow">
|
||||||
|
<v-col
|
||||||
|
v-for="(control, keyControl) in row" :key="keyControl"
|
||||||
|
:cols="12 / row.length"
|
||||||
|
class="py-2 mx-0"
|
||||||
|
style="display: flex; align-items: center; justify-content: center;"
|
||||||
|
>
|
||||||
|
<v-btn icon :color="control.color" @click="control.callback()">
|
||||||
|
<v-icon> {{ control.icon }} </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer />
|
||||||
|
<v-col cols="8" align-self="center">
|
||||||
|
<Cropper
|
||||||
|
ref="cropper"
|
||||||
|
class="cropper"
|
||||||
|
:src="img"
|
||||||
|
:default-size="defaultSize"
|
||||||
|
:style="`height: ${cropperHeight}; width: ${cropperWidth};`"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
|
import { Cropper } from "vue-advanced-cropper";
|
||||||
|
import "vue-advanced-cropper/dist/style.css";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { Cropper },
|
||||||
|
props: {
|
||||||
|
img: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
cropperHeight: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
cropperWidth: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
const cropper = ref<Cropper>();
|
||||||
|
const { $globals, $vuetify } = useContext();
|
||||||
|
|
||||||
|
interface Control {
|
||||||
|
color: string;
|
||||||
|
icon: string;
|
||||||
|
callback: CallableFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controls = ref<Control[][]>([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
color: "info",
|
||||||
|
icon: $globals.icons.flipHorizontal,
|
||||||
|
callback: () => flip(true, false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "info",
|
||||||
|
icon: $globals.icons.flipVertical,
|
||||||
|
callback: () => flip(false, true),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
color: "info",
|
||||||
|
icon: $globals.icons.rotateLeft,
|
||||||
|
callback: () => rotate(-90),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "info",
|
||||||
|
icon: $globals.icons.rotateRight,
|
||||||
|
callback: () => rotate(90),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
color: "success",
|
||||||
|
icon: $globals.icons.save,
|
||||||
|
callback: () => save(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
function flip(hortizontal: boolean, vertical?: boolean) {
|
||||||
|
if (!cropper.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cropper.value.flip(hortizontal, vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotate(angle: number) {
|
||||||
|
if (!cropper.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cropper.value.rotate(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
if (!cropper.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { canvas } = cropper.value.getResult();
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
context.emit("save", blob);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cropper,
|
||||||
|
controls,
|
||||||
|
flip,
|
||||||
|
rotate,
|
||||||
|
save,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// @ts-expect-error https://advanced-cropper.github.io/vue-advanced-cropper/guides/advanced-recipes.html
|
||||||
|
defaultSize({ imageSize, visibleArea }) {
|
||||||
|
return {
|
||||||
|
width: (visibleArea || imageSize).width,
|
||||||
|
height: (visibleArea || imageSize).height,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -456,6 +456,7 @@
|
||||||
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD format",
|
"date-format-hint-yyyy-mm-dd": "YYYY-MM-DD format",
|
||||||
"add-to-list": "Add to List",
|
"add-to-list": "Add to List",
|
||||||
"add-to-plan": "Add to Plan",
|
"add-to-plan": "Add to Plan",
|
||||||
|
"add-to-timeline": "Add to Timeline",
|
||||||
"recipe-added-to-list": "Recipe added to list",
|
"recipe-added-to-list": "Recipe added to list",
|
||||||
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
||||||
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
||||||
|
@ -529,7 +530,8 @@
|
||||||
"tree-view": "Tree View",
|
"tree-view": "Tree View",
|
||||||
"recipe-yield": "Recipe Yield",
|
"recipe-yield": "Recipe Yield",
|
||||||
"unit": "Unit",
|
"unit": "Unit",
|
||||||
"upload-image": "Upload image"
|
"upload-image": "Upload image",
|
||||||
|
"remove-image": "Remove image"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"advanced-search": "Advanced Search",
|
"advanced-search": "Advanced Search",
|
||||||
|
|
|
@ -196,10 +196,10 @@ export class RecipeAPI extends BaseCRUDAPI<CreateRecipe, Recipe, Recipe> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateTimelineEventImage(eventId: string, fileObject: File) {
|
async updateTimelineEventImage(eventId: string, fileObject: Blob | File, fileName: string) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("image", fileObject);
|
formData.append("image", fileObject);
|
||||||
formData.append("extension", fileObject.name.split(".").pop() ?? "");
|
formData.append("extension", fileName.split(".").pop() ?? "");
|
||||||
|
|
||||||
return await this.requests.put<UpdateImageResponse, FormData>(routes.recipesTimelineEventIdImage(eventId), formData);
|
return await this.requests.put<UpdateImageResponse, FormData>(routes.recipesTimelineEventIdImage(eventId), formData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,10 @@ import {
|
||||||
mdiDockTop,
|
mdiDockTop,
|
||||||
mdiDockBottom,
|
mdiDockBottom,
|
||||||
mdiCheckboxOutline,
|
mdiCheckboxOutline,
|
||||||
|
mdiFlipHorizontal,
|
||||||
|
mdiFlipVertical,
|
||||||
|
mdiRotateLeft,
|
||||||
|
mdiRotateRight,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
|
@ -200,6 +204,8 @@ export const icons = {
|
||||||
fileImage: mdiFileImage,
|
fileImage: mdiFileImage,
|
||||||
filePDF: mdiFilePdfBox,
|
filePDF: mdiFilePdfBox,
|
||||||
filter: mdiFilter,
|
filter: mdiFilter,
|
||||||
|
flipHorizontal: mdiFlipHorizontal,
|
||||||
|
flipVertical: mdiFlipVertical,
|
||||||
folderOutline: mdiFolderOutline,
|
folderOutline: mdiFolderOutline,
|
||||||
food: mdiFood,
|
food: mdiFood,
|
||||||
formatColorFill: mdiFormatColorFill,
|
formatColorFill: mdiFormatColorFill,
|
||||||
|
@ -226,6 +232,8 @@ export const icons = {
|
||||||
printerSettings: mdiPrinterPosCog,
|
printerSettings: mdiPrinterPosCog,
|
||||||
refreshCircle: mdiRefreshCircle,
|
refreshCircle: mdiRefreshCircle,
|
||||||
robot: mdiRobot,
|
robot: mdiRobot,
|
||||||
|
rotateLeft: mdiRotateLeft,
|
||||||
|
rotateRight: mdiRotateRight,
|
||||||
search: mdiMagnify,
|
search: mdiMagnify,
|
||||||
shareVariant: mdiShareVariant,
|
shareVariant: mdiShareVariant,
|
||||||
shuffleVariant: mdiShuffleVariant,
|
shuffleVariant: mdiShuffleVariant,
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"isomorphic-dompurify": "^1.0.0",
|
"isomorphic-dompurify": "^1.0.0",
|
||||||
"nuxt": "^2.16.0",
|
"nuxt": "^2.16.0",
|
||||||
"v-jsoneditor": "^1.4.5",
|
"v-jsoneditor": "^1.4.5",
|
||||||
|
"vue-advanced-cropper": "^1.11.6",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"vuetify": "^2.6.13"
|
"vuetify": "^2.6.13"
|
||||||
},
|
},
|
||||||
|
|
2
frontend/types/components.d.ts
vendored
2
frontend/types/components.d.ts
vendored
|
@ -21,6 +21,7 @@ import DevDumpJson from "@/components/global/DevDumpJson.vue";
|
||||||
import DocLink from "@/components/global/DocLink.vue";
|
import DocLink from "@/components/global/DocLink.vue";
|
||||||
import DropZone from "@/components/global/DropZone.vue";
|
import DropZone from "@/components/global/DropZone.vue";
|
||||||
import HelpIcon from "@/components/global/HelpIcon.vue";
|
import HelpIcon from "@/components/global/HelpIcon.vue";
|
||||||
|
import ImageCropper from "@/components/global/ImageCropper.vue";
|
||||||
import InputColor from "@/components/global/InputColor.vue";
|
import InputColor from "@/components/global/InputColor.vue";
|
||||||
import InputLabelType from "@/components/global/InputLabelType.vue";
|
import InputLabelType from "@/components/global/InputLabelType.vue";
|
||||||
import InputQuantity from "@/components/global/InputQuantity.vue";
|
import InputQuantity from "@/components/global/InputQuantity.vue";
|
||||||
|
@ -61,6 +62,7 @@ declare module "vue" {
|
||||||
DocLink: typeof DocLink;
|
DocLink: typeof DocLink;
|
||||||
DropZone: typeof DropZone;
|
DropZone: typeof DropZone;
|
||||||
HelpIcon: typeof HelpIcon;
|
HelpIcon: typeof HelpIcon;
|
||||||
|
ImageCropper: typeof ImageCropper;
|
||||||
InputColor: typeof InputColor;
|
InputColor: typeof InputColor;
|
||||||
InputLabelType: typeof InputLabelType;
|
InputLabelType: typeof InputLabelType;
|
||||||
InputQuantity: typeof InputQuantity;
|
InputQuantity: typeof InputQuantity;
|
||||||
|
|
|
@ -3735,6 +3735,11 @@ class-utils@^0.3.5:
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
|
classnames@^2.2.6:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||||
|
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||||
|
|
||||||
clean-css@^4.2.1, clean-css@^4.2.3:
|
clean-css@^4.2.1, clean-css@^4.2.3:
|
||||||
version "4.2.4"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
|
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
|
||||||
|
@ -4277,6 +4282,11 @@ de-indent@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||||
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
||||||
|
|
||||||
|
debounce@^1.2.0:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
|
||||||
|
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
@ -4584,6 +4594,11 @@ eastasianwidth@^0.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||||
|
|
||||||
|
easy-bem@^1.0.2:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/easy-bem/-/easy-bem-1.1.1.tgz#1bfcc10425498090bcfddc0f9c000aba91399e03"
|
||||||
|
integrity sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==
|
||||||
|
|
||||||
ee-first@1.1.1:
|
ee-first@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
@ -11032,6 +11047,15 @@ vm-browserify@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||||
|
|
||||||
|
vue-advanced-cropper@^1.11.6:
|
||||||
|
version "1.11.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-advanced-cropper/-/vue-advanced-cropper-1.11.6.tgz#38f824e515747d749168e20de6d5eeea1a8d508b"
|
||||||
|
integrity sha512-S/3VXfnvq/8C3Js6OaxfPN709l7mrWRqI4GRklGM08glyXF147Nl74EkfyVNv7zhuNLM4stPvaQB7XUvRH9/iA==
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.6"
|
||||||
|
debounce "^1.2.0"
|
||||||
|
easy-bem "^1.0.2"
|
||||||
|
|
||||||
vue-client-only@^2.1.0:
|
vue-client-only@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/vue-client-only/-/vue-client-only-2.1.0.tgz#1a67a47b8ecacfa86d75830173fffee3bf8a4ee3"
|
resolved "https://registry.yarnpkg.com/vue-client-only/-/vue-client-only-2.1.0.tgz#1a67a47b8ecacfa86d75830173fffee3bf8a4ee3"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue