mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-24 02:39:20 -05:00
basic user file upload working
This commit is contained in:
@@ -233,15 +233,15 @@ class FoodInheritFieldSerializer(UniqueFieldsMixin, WritableNestedModelSerialize
|
||||
|
||||
class UserFileSerializer(serializers.ModelSerializer):
|
||||
created_by = UserSerializer(read_only=True)
|
||||
file = serializers.FileField(write_only=True)
|
||||
file = serializers.FileField(write_only=True, required=False)
|
||||
file_download = serializers.SerializerMethodField('get_download_link')
|
||||
preview = serializers.SerializerMethodField('get_preview_link')
|
||||
|
||||
@extend_schema_field(str)
|
||||
@extend_schema_field(serializers.CharField(read_only=True))
|
||||
def get_download_link(self, obj):
|
||||
return self.context['request'].build_absolute_uri(reverse('api_download_file', args={obj.pk}))
|
||||
|
||||
@extend_schema_field(str)
|
||||
@extend_schema_field(serializers.CharField(read_only=True))
|
||||
def get_preview_link(self, obj):
|
||||
try:
|
||||
Image.open(obj.file.file.file)
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
{% vite_hmr_client %}
|
||||
{% vite_asset 'src/apps/tandoor/main.ts' %}
|
||||
|
||||
<script type="application/javascript">
|
||||
localStorage.setItem('BASE_PATH', "{% base_path request 'base' %}")
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.5.7",
|
||||
"vue-draggable-plus": "^0.6.0",
|
||||
"vue-i18n": "9",
|
||||
"vue-i18n": "10.0.5",
|
||||
"vue-router": "4",
|
||||
"vue-simple-calendar": "^7.1.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "^3.7.3"
|
||||
"vuetify": "^3.7.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
|
||||
<v-avatar color="primary" class="me-2">{{ useUserPreferenceStore().userSettings.user.displayName.charAt(0) }}
|
||||
<v-avatar color="primary" class="me-2 cursor-pointer">{{ useUserPreferenceStore().userSettings.user.displayName.charAt(0) }}
|
||||
<v-menu activator="parent">
|
||||
|
||||
<v-list density="compact">
|
||||
@@ -55,7 +55,7 @@
|
||||
{{ $t('Messages') }}
|
||||
<message-list-dialog></message-list-dialog>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item :href="getDjangoUrl('accounts/logout')" link>
|
||||
<template #prepend>
|
||||
<v-icon icon="fa-solid fa-arrow-right-from-bracket"></v-icon>
|
||||
</template>
|
||||
@@ -83,7 +83,7 @@
|
||||
</v-main>
|
||||
|
||||
<v-navigation-drawer v-if="lgAndUp">
|
||||
<v-list-item>
|
||||
<v-list-item :to="{ name: 'view_settings', params: {} }">
|
||||
<template #prepend>
|
||||
<v-avatar color="primary">{{ useUserPreferenceStore().userSettings.user.displayName.charAt(0) }}</v-avatar>
|
||||
</template>
|
||||
@@ -150,8 +150,10 @@ import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {TAutomation, TCookLog, TFood, TKeyword, TPropertyType, TSupermarket, TSupermarketCategory, TUnit, TUnitConversion, TUserFile, TViewLog} from "@/types/Models";
|
||||
import NavigationDrawerContextMenu from "@/components/display/NavigationDrawerContextMenu.vue";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
|
||||
const {lgAndUp} = useDisplay()
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
useUserPreferenceStore()
|
||||
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
<v-textarea :label="$t('Description')" v-model="editingObj.description" clearable counter="512"></v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label>{{ $t('Image')}}</v-label>
|
||||
<v-img style="max-height: 150px" :src="editingObj.image"></v-img>
|
||||
<v-file-input></v-file-input>
|
||||
<v-btn color="delete" prepend-icon="$delete" v-if="editingObj.image">{{$t('Delete')}}</v-btn>
|
||||
<v-btn color="success" prepend-icon="$upload">{{$t('Select')}}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
<v-label>{{ $t('Keywords') }}</v-label>
|
||||
<ModelSelect mode="tags" v-model="editingObj.keywords" model="Keyword"></ModelSelect>
|
||||
<v-row>
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
<template>
|
||||
<model-editor-base
|
||||
:loading="loading"
|
||||
:loading="loading || fileApiLoading"
|
||||
:dialog="dialog"
|
||||
@save="saveObject"
|
||||
@save="saveFile"
|
||||
@delete="deleteObject"
|
||||
@close="emit('close')"
|
||||
:is-update="isUpdate()"
|
||||
:model-class="modelClass"
|
||||
:object-name="editingObjName()">
|
||||
<v-card-text>
|
||||
<v-form :disabled="loading">
|
||||
<v-form :disabled="loading || fileApiLoading">
|
||||
|
||||
<v-text-field :label="$t('Name')" v-model="editingObj.name"></v-text-field>
|
||||
<!-- <v-file-input :label="$t('File')" v-model="editingObj.file" @change="uploadFile()"></v-file-input>-->
|
||||
<!-- <v-file-input :label="$t('File')" v-model="editingObj.file" @change="uploadFile()"></v-file-input>-->
|
||||
|
||||
<v-label> {{ $t('Preview') }}</v-label>
|
||||
<v-img max-height="25vh" rounded :src="editingObj.preview"></v-img>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-file-upload v-model="file"></v-file-upload>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label> {{ $t('Preview') }}</v-label>
|
||||
<v-img max-height="25vh" rounded :src="editingObj.preview"></v-img>
|
||||
<v-btn :href="editingObj.fileDownload" target="_blank" color="success" class="float-right" prepend-icon="fa-solid fa-file-arrow-down">{{ $t('Download') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-alert v-if="!loading">
|
||||
<v-alert class="mt-2" v-if="!loading && !fileApiLoading && Object.keys(editingObj).length > 0">
|
||||
{{ $n(editingObj.fileSizeKb / 1000) }} MB <br/>
|
||||
{{ editingObj.createdBy.displayName }} <br/>
|
||||
{{ DateTime.fromJSDate(editingObj.createdAt).toLocaleString(DateTime.DATETIME_SHORT) }}
|
||||
<template #append>
|
||||
<v-btn :href="editingObj.fileDownload" target="_blank" color="success" prepend-icon="fa-solid fa-file-arrow-down">{{ $t('Download') }}</v-btn>
|
||||
</template>
|
||||
</v-alert>
|
||||
|
||||
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</model-editor-base>
|
||||
@@ -35,18 +39,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, PropType} from "vue";
|
||||
import {Keyword, UserFile, UserFileFromJSON} from "@/openapi";
|
||||
import {onMounted, PropType, shallowRef} from "vue";
|
||||
import {UserFile} from "@/openapi";
|
||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {DateTime} from "luxon";
|
||||
import UserFileField from "@/components/inputs/UserFileField.vue";
|
||||
import {getCookie} from "@/utils/cookie";
|
||||
import {VFileUpload} from 'vuetify/labs/VFileUpload'
|
||||
import {useFileApi} from "@/composables/useFileApi";
|
||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
item: {type: {} as PropType<UserFile>, required: false, default: null},
|
||||
itemId: {type: [Number, String], required: false, default: undefined},
|
||||
@@ -55,53 +56,25 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['create', 'save', 'delete', 'close'])
|
||||
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading, editingObj, modelClass} = useModelEditorFunctions<UserFile>('UserFile', emit)
|
||||
const {fileApiLoading, createOrUpdateUserFile} = useFileApi()
|
||||
|
||||
// object specific data (for selects/display)
|
||||
|
||||
const file = shallowRef<File | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
setupState(props.item, props.itemId)
|
||||
})
|
||||
|
||||
// TODO this is aweful, need to fix but works for now
|
||||
function uploadFile() {
|
||||
loading.value = true
|
||||
let formData = new FormData()
|
||||
formData.append('file', editingObj.value.file)
|
||||
formData.append('name', editingObj.value.name)
|
||||
|
||||
if (isUpdate()) {
|
||||
//TODO proper URL finding (sub path setups)
|
||||
fetch('/api/user-file/' + editingObj.value.id + '/', {
|
||||
method: 'PUT',
|
||||
headers: {'X-CSRFToken': getCookie('csrftoken')},
|
||||
body: formData
|
||||
}).then(r => { // TODO maybe better use existing URL clients response functions for parsing
|
||||
r.json().then(r => {
|
||||
editingObj.value = UserFileFromJSON(r)
|
||||
})
|
||||
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
//TODO proper URL finding (sub path setups)
|
||||
fetch('/api/user-file/', {
|
||||
method: 'POST',
|
||||
headers: {'X-CSRFToken': getCookie('csrftoken')},
|
||||
body: formData
|
||||
}).then(r => { // TODO maybe better use existing URL clients response functions for parsing
|
||||
r.json().then(r => {
|
||||
editingObj.value = UserFileFromJSON(r)
|
||||
})
|
||||
useMessageStore().addPreparedMessage(PreparedMessage.CREATE_SUCCESS)
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
function saveFile() {
|
||||
|
||||
createOrUpdateUserFile(editingObj.value.name, file.value, editingObj.value.id).then(r => {
|
||||
editingObj.value = r
|
||||
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
|
||||
<p class="text-h6 mt-3">{{ $t('Account') }}</p>
|
||||
<v-divider class="mb-3"></v-divider>
|
||||
<v-btn color="primary" class="mt-1" href="/accounts/email/" target="_blank">{{ $t('Manage_Emails') }}</v-btn>
|
||||
<v-btn color="primary" class="mt-1" :href="getDjangoUrl('accounts/email/')" target="_blank">{{ $t('Manage_Emails') }}</v-btn>
|
||||
<br/>
|
||||
<v-btn color="primary" class="mt-1" href="/accounts/password/change/" target="_blank">{{ $t('Change_Password') }}</v-btn>
|
||||
<v-btn color="primary" class="mt-1" :href="getDjangoUrl('accounts/password/change/')" target="_blank">{{ $t('Change_Password') }}</v-btn>
|
||||
<br/>
|
||||
<v-btn color="primary" class="mt-1" href="/accounts/social/connections/" target="_blank">{{ $t('Social_Authentication') }}</v-btn>
|
||||
<v-btn color="primary" class="mt-1" :href="getDjangoUrl('accounts/social/connections/')" target="_blank">{{ $t('Social_Authentication') }}</v-btn>
|
||||
<br/>
|
||||
|
||||
<p class="text-h6 mt-3">{{ $t('DeviceSettings') }}</p>
|
||||
@@ -35,7 +35,9 @@ import {onMounted, ref} from "vue";
|
||||
import {ApiApi, User} from "@/openapi";
|
||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
const user = ref({} as User)
|
||||
|
||||
|
||||
21
vue3/src/composables/useDjangoUrls.ts
Normal file
21
vue3/src/composables/useDjangoUrls.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* helper function to use django urls while respecting sub path setups
|
||||
* only needed as long as not all pages are integrated into the Vue.js frontend (which might be forever...)
|
||||
*/
|
||||
export function useDjangoUrls() {
|
||||
const basePath = localStorage.getItem('BASE_PATH')
|
||||
|
||||
/**
|
||||
* given a path return the full server url to that url respecting possible sub path setups
|
||||
* @param path
|
||||
*/
|
||||
function getDjangoUrl(path: string){
|
||||
if(path.charAt(0) == '/'){
|
||||
path = path.substring(1)
|
||||
}
|
||||
|
||||
return `${basePath}/${path}`
|
||||
}
|
||||
|
||||
return {basePath, getDjangoUrl}
|
||||
}
|
||||
46
vue3/src/composables/useFileApi.ts
Normal file
46
vue3/src/composables/useFileApi.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
import {ref} from "vue";
|
||||
import {getCookie} from "@/utils/cookie";
|
||||
import {UserFile, UserFileFromJSON} from "@/openapi";
|
||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
|
||||
/**
|
||||
* function to upload files to the multipart endpoints accepting file uploads
|
||||
*/
|
||||
export function useFileApi() {
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
const fileApiLoading = ref(false)
|
||||
|
||||
function createOrUpdateUserFile(name: string, file: File | null, id?: number): Promise<UserFile> {
|
||||
let formData = new FormData()
|
||||
formData.append('name', name)
|
||||
|
||||
if (file != null) {
|
||||
formData.append('file', file)
|
||||
}
|
||||
|
||||
fileApiLoading.value = true
|
||||
|
||||
let fetchUrl = getDjangoUrl('api/user-file/')
|
||||
let fetchMethod = 'POST'
|
||||
if (id) {
|
||||
fetchUrl += `${id}/`
|
||||
fetchMethod = 'PUT'
|
||||
}
|
||||
|
||||
return fetch(fetchUrl, {
|
||||
method: fetchMethod,
|
||||
headers: {'X-CSRFToken': getCookie('csrftoken')},
|
||||
body: formData
|
||||
}).then(r => {
|
||||
return r.json().then(r => {
|
||||
return UserFileFromJSON(r)
|
||||
})
|
||||
}).finally(() => {
|
||||
fileApiLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
return {fileApiLoading, createOrUpdateUserFile}
|
||||
}
|
||||
@@ -164,3 +164,4 @@ models/UserPreferenceNavTextColorEnum.ts
|
||||
models/UserSpace.ts
|
||||
models/ViewLog.ts
|
||||
models/index.ts
|
||||
runtime.ts
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import {getCookie} from "@/utils/cookie";
|
||||
|
||||
/**
|
||||
* Django Recipes
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
* Tandoor
|
||||
* Tandoor API Docs
|
||||
*
|
||||
* The version of the OpenAPI document:
|
||||
* The version of the OpenAPI document: 0.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
@@ -312,11 +312,6 @@ export interface RequestOpts {
|
||||
body?: HTTPBody;
|
||||
}
|
||||
|
||||
export function exists(json: any, key: string) {
|
||||
const value = json[key];
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export function querystring(params: HTTPQuery, prefix: string = ''): string {
|
||||
return Object.keys(params)
|
||||
.map(key => querystringSingleKey(key, params[key], prefix))
|
||||
@@ -344,6 +339,11 @@ function querystringSingleKey(key: string, value: string | number | null | undef
|
||||
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
|
||||
}
|
||||
|
||||
export function exists(json: any, key: string) {
|
||||
const value = json[key];
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export function mapValues(data: any, fn: (item: any) => any) {
|
||||
return Object.keys(data).reduce(
|
||||
(acc, key) => ({ ...acc, [key]: fn(data[key]) }),
|
||||
|
||||
@@ -65,24 +65,12 @@ import {
|
||||
GenericModel,
|
||||
getGenericModelFromString, getListModels,
|
||||
Model,
|
||||
TAutomation,
|
||||
TCookLog,
|
||||
TFood,
|
||||
TKeyword,
|
||||
TPropertyType,
|
||||
TSupermarket,
|
||||
TSupermarketCategory,
|
||||
TUnit,
|
||||
TUnitConversion,
|
||||
TUserFile,
|
||||
TViewLog
|
||||
} from "@/types/Models";
|
||||
import {VDataTable} from "vuetify/components";
|
||||
import {useUrlSearchParams} from "@vueuse/core";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {ResponseError} from "@/openapi";
|
||||
|
||||
type VDataTableProps = InstanceType<typeof VDataTable>['$props']
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<v-divider></v-divider>
|
||||
<v-list-subheader>Admin</v-list-subheader>
|
||||
<v-list-item :to="{name: 'view_settings_api'}" prepend-icon="fa-solid fa-code">{{ $t('API') }}</v-list-item>
|
||||
<v-list-item href="/system/" prepend-icon="fa-solid fa-server">{{ $t('System') }}</v-list-item>
|
||||
<v-list-item :href="getDjangoUrl('system')" target="_blank" prepend-icon="fa-solid fa-server">{{ $t('System') }}</v-list-item>
|
||||
</v-list>
|
||||
|
||||
</v-col>
|
||||
@@ -34,12 +34,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
|
||||
import AccountSettings from "@/components/settings/AccountSettings.vue";
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
const props = defineProps({
|
||||
page: {type: String, default: 'ACCOUNT'}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ export default createVuetify({
|
||||
delete: 'fa-solid fa-trash-can',
|
||||
edit: 'fa-solid fa-pencil',
|
||||
create: 'fa-solid fa-plus',
|
||||
upload: 'fa-solid fa-file-arrow-up',
|
||||
search: 'fa-solid fa-magnifying-glass',
|
||||
copy: 'fa-solid fa-copy',
|
||||
add: 'fa-solid fa-plus',
|
||||
|
||||
@@ -158,26 +158,26 @@
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz#0e984f0f2344ee513c185d87d77defac4c0c8224"
|
||||
integrity sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==
|
||||
|
||||
"@intlify/core-base@9.13.1":
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.13.1.tgz#bd1f38e665095993ef9b67aeeb794f3cabcb515d"
|
||||
integrity sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==
|
||||
"@intlify/core-base@10.0.5":
|
||||
version "10.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-10.0.5.tgz#c4d992381f8c3a50c79faf67be3404b399c3be28"
|
||||
integrity sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==
|
||||
dependencies:
|
||||
"@intlify/message-compiler" "9.13.1"
|
||||
"@intlify/shared" "9.13.1"
|
||||
"@intlify/message-compiler" "10.0.5"
|
||||
"@intlify/shared" "10.0.5"
|
||||
|
||||
"@intlify/message-compiler@9.13.1":
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.13.1.tgz#ff8129badf77db3fb648b8d3cceee87c8033ed0a"
|
||||
integrity sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==
|
||||
"@intlify/message-compiler@10.0.5":
|
||||
version "10.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-10.0.5.tgz#4eeace9f4560020d5e5d77f32bed7755e71d8efd"
|
||||
integrity sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==
|
||||
dependencies:
|
||||
"@intlify/shared" "9.13.1"
|
||||
"@intlify/shared" "10.0.5"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
"@intlify/shared@9.13.1":
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.13.1.tgz#202741d11ece1a9c7480bfd3f27afcf9cb8f72e4"
|
||||
integrity sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==
|
||||
"@intlify/shared@10.0.5":
|
||||
version "10.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-10.0.5.tgz#1b46ca8b541f03508fe28da8f34e4bb85506d6bc"
|
||||
integrity sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.15":
|
||||
version "1.4.15"
|
||||
@@ -1120,13 +1120,13 @@ vue-draggable-plus@^0.6.0:
|
||||
dependencies:
|
||||
"@types/sortablejs" "^1.15.8"
|
||||
|
||||
vue-i18n@9:
|
||||
version "9.13.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.13.1.tgz#a292c8021b7be604ebfca5609ae1f8fafe5c36d7"
|
||||
integrity sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==
|
||||
vue-i18n@10.0.5:
|
||||
version "10.0.5"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-10.0.5.tgz#fdf4e6c7b669e80cfa3a12ed9625e2b46671cdf0"
|
||||
integrity sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==
|
||||
dependencies:
|
||||
"@intlify/core-base" "9.13.1"
|
||||
"@intlify/shared" "9.13.1"
|
||||
"@intlify/core-base" "10.0.5"
|
||||
"@intlify/shared" "10.0.5"
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
|
||||
vue-router@4:
|
||||
@@ -1189,10 +1189,10 @@ vuedraggable@^4.1.0:
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
vuetify@^3.7.3:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.7.3.tgz#0e89f7f0298d452510bcbc01b0e9b53a5ce6e883"
|
||||
integrity sha512-bpuvBpZl1/+nLlXDgdVXekvMNR6W/ciaoa8CYlpeAzAARbY8zUFSoBq05JlLhkIHI58AnzKVy4c09d0OtfYAPg==
|
||||
vuetify@^3.7.6:
|
||||
version "3.7.6"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.7.6.tgz#f966dbe74666cb847d67b3e3d8c6fee75cf972d3"
|
||||
integrity sha512-lol0Va5HtMIqZfjccSD5DLv5v31R/asJXzc6s7ULy51PHr1DjXxWylZejhq0kVpMGW64MiV1FmA/p8eYQfOWfQ==
|
||||
|
||||
w3c-xmlserializer@^5.0.0:
|
||||
version "5.0.0"
|
||||
|
||||
Reference in New Issue
Block a user