Add "upload" page

This commit is contained in:
Pig Fang 2018-08-15 16:44:21 +08:00
parent a573f0efe3
commit 733e694ba0
11 changed files with 399 additions and 181 deletions

View File

@ -25,7 +25,6 @@
"@fortawesome/fontawesome-free": "^5.2.0", "@fortawesome/fontawesome-free": "^5.2.0",
"admin-lte": "^2.4.2", "admin-lte": "^2.4.2",
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"bootstrap-fileinput": "^4.4.7",
"chart.js": "^2.7.1", "chart.js": "^2.7.1",
"es6-promise": "^4.2.4", "es6-promise": "^4.2.4",
"highlight.js": "^9.12.0", "highlight.js": "^9.12.0",
@ -36,6 +35,7 @@
"toastr": "^2.1.4", "toastr": "^2.1.4",
"vue": "^2.5.16", "vue": "^2.5.16",
"vue-good-table": "^2.12.2", "vue-good-table": "^2.12.2",
"vue-upload-component": "^2.8.11",
"vuejs-paginate": "^2.0.1", "vuejs-paginate": "^2.0.1",
"whatwg-fetch": "^2.0.4" "whatwg-fetch": "^2.0.4"
}, },

View File

@ -59,4 +59,9 @@ export default [
component: () => import('./skinlib/List'), component: () => import('./skinlib/List'),
el: '.content-wrapper' el: '.content-wrapper'
}, },
{
path: 'skinlib/upload',
component: () => import('./skinlib/Upload'),
el: '.content'
},
]; ];

View File

@ -0,0 +1,222 @@
<template>
<section class="content">
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-body">
<div class="form-group">
<label for="name" v-t="'skinlib.upload.texture-name'"></label>
<input
v-model="name"
class="form-control"
type="text"
:placeholder="textureNameRule"
/>
</div>
<div class="form-group">
<label v-t="'skinlib.upload.texture-type'"></label>
<br>
<label>
<input
type="radio"
v-model="type"
name="type"
value="steve"
checked
> Steve
</label>&nbsp;
<label>
<input
type="radio"
v-model="type"
name="type"
value="alex"
> Alex
</label>&nbsp;
<label>
<input
type="radio"
v-model="type"
name="type"
value="cape"
> {{ $t('general.cape') }}
</label>
</div>
<div class="form-group">
<label for="file" v-t="'skinlib.upload.select-file'"></label>
<div class="file-dnd">
<img v-if="hasFile" :src="texture">
<h3 v-else v-t="'skinlib.upload.dropZone'"></h3>
</div>
<file-upload
v-model="files"
ref="upload"
extensions="png"
accept="image/png,image/x-png"
drop=".file-dnd"
@input-file="inputFile"
>
<span class="btn btn-primary">
{{ $t('skinlib.upload.select-file') }}
</span>
</file-upload>
<button
v-show="hasFile"
class="btn btn-default pull-right"
@click="remove"
>
<i class="fas fa-trash-alt"></i>
{{ $t('skinlib.upload.remove') }}
</button>
</div>
<div class="callout callout-info" v-if="isPrivate">
<p>{{ privacyNotice }}</p>
</div>
</div><!-- /.box-body -->
<div class="box-footer">
<label
for="private"
class="pull-right"
:title="$t('skinlib.upload.privacy-notice')"
data-placement="top"
data-toggle="tooltip"
>
<input v-model="isPrivate" type="checkbox"> {{ $t('skinlib.upload.set-as-private') }}
</label>
<button v-if="uploading" class="btn btn-primary" disabled>
<i class="fa fa-spinner fa-spin"></i> {{ $t('skinlib.uploading') }}
</button>
<button v-else @click="upload" class="btn btn-primary">
{{ $t('skinlib.upload.button') }}
</button>
&nbsp; {{ hasFile && $t('skinlib.upload.cost', { score: scoreCost }) }}
</div>
</div><!-- /.box -->
</div>
<div class="col-md-6">
<previewer
:skin="type !== 'cape' && texture"
:cape="type === 'cape' ? texture : ''"
></previewer>
</div>
</div>
</section>
</template>
<script>
import FileUpload from 'vue-upload-component';
import toastr from 'toastr';
import { walkFetch } from '../../js/net';
import { swal } from '../../js/notify';
export default {
name: 'Upload',
components: {
Previewer: () => import('../common/Previewer'),
FileUpload
},
data() {
return {
name: '',
type: 'steve',
isPrivate: false,
files: [],
texture: '',
uploading: false
};
},
computed: {
textureNameRule: () => __bs_data__.rule,
privacyNotice: () => __bs_data__.privacyNotice,
scorePublic: () => __bs_data__.scorePublic,
scorePrivate: () => __bs_data__.scorePrivate,
scoreCost() {
const size = Math.round(this.files[0].size / 1024) || 1;
return size * (this.isPrivate ? this.scorePrivate : this.scorePublic);
},
hasFile() {
return this.files[0];
}
},
methods: {
async upload() {
if (!this.hasFile) {
toastr.info(this.$t('skinlib.emptyUploadFile'));
return;
}
if (!this.name) {
toastr.info(this.$t('skinlib.emptyTextureName'));
return;
}
if (!/image\/(x-)?png/.test(this.files[0].type)) {
toastr.info(this.$t('skinlib.fileExtError'));
return;
}
const data = new FormData();
data.append('name', this.name);
data.append('type', this.type);
data.append('file', this.files[0].file, this.files[0].name);
data.append('public', !this.isPrivate);
this.uploading = true;
const request = new Request(`${blessing.base_url}/skinlib/upload`, {
body: data,
credentials: 'same-origin',
headers: {
Accept: 'application/json'
},
method: 'POST'
});
const { errno, msg, tid } = await walkFetch(request);
if (errno === 0) {
await swal({ type: 'success', text: msg });
toastr.info(this.$t('skinlib.redirecting'));
setTimeout(() => {
window.location = (`${blessing.base_url}/skinlib/show/${tid}`);
}, 1000);
} else {
await swal({ type: 'warning', text: msg });
this.uploading = false;
}
},
inputFile(file) {
if (!file) return;
if (!this.name) {
const matched = /(.*)\.png$/i.exec(file.name);
this.name = matched ? matched[1] : file.name;
}
this.texture = URL.createObjectURL(file.file);
},
remove() {
this.$refs.upload.clear();
this.texture = '';
}
}
};
</script>
<style lang="stylus">
.file-dnd {
min-height: 256px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 2px;
display: flex;
justify-content: center;
align-items: center;
h3 {
color: #aaa;
}
}
</style>

View File

@ -1,6 +1,5 @@
import './jquery'; // jQuery first import './jquery'; // jQuery first
import 'bootstrap'; import 'bootstrap';
import 'bootstrap-fileinput';
import 'admin-lte'; import 'admin-lte';
import 'icheck'; import 'icheck';
import Vue from 'vue'; import Vue from 'vue';

View File

@ -10,7 +10,7 @@ const init = {
} }
}; };
async function walkFetch(request) { export async function walkFetch(request) {
try { try {
const response = await fetch(request); const response = await fetch(request);
if (response.ok) { if (response.ok) {

View File

@ -0,0 +1,138 @@
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import Upload from '@/components/skinlib/Upload';
import { flushPromises } from '../../utils';
import toastr from 'toastr';
import { swal } from '@/js/notify';
import { walkFetch } from '@/js/net';
jest.mock('toastr');
jest.mock('@/js/notify');
jest.mock('@/js/net');
window.__bs_data__ = {
textureNameRule: 'rule',
privacyNotice: 'privacyNotice',
scorePrivate: 10,
scorePublic: 1
};
test('display drap and drop notice', () => {
const wrapper = mount(Upload, {
stubs: ['file-upload']
});
expect(wrapper.text()).toContain('skinlib.upload.dropZone');
wrapper.setData({ files: [{}] });
expect(wrapper.contains('img')).toBeTrue();
});
test('button for removing texture', () => {
const wrapper = mount(Upload, {
stubs: ['file-upload']
});
wrapper.vm.$refs = {
upload: {
clear: jest.fn()
}
};
const button = wrapper.find('.btn-default');
expect(button.isVisible()).toBeFalse();
wrapper.setData({ files: [{}] });
expect(button.isVisible()).toBeTrue();
button.trigger('click');
expect(wrapper.vm.texture).toBe('');
});
test('notice should be display if texture is private', () => {
const wrapper = mount(Upload, {
stubs: ['file-upload']
});
expect(wrapper.contains('.callout')).toBeFalse();
wrapper.find('[type=checkbox]').setChecked();
expect(wrapper.find('.callout').text()).toBe('privacyNotice');
});
test('display score cost', () => {
const origin = Vue.prototype.$t;
Vue.prototype.$t = (key, args) => `${key}${JSON.stringify(args)}`;
const wrapper = mount(Upload, {
stubs: ['file-upload']
});
wrapper.find('[type=checkbox]').setChecked();
wrapper.setData({ files: [{ size: 1024 }] });
expect(wrapper.text()).toContain(JSON.stringify({ score: 10 }));
Vue.prototype.$t = origin;
});
test('process input file', () => {
window.URL.createObjectURL = jest.fn().mockReturnValue('file-url');
const blob = new Blob;
const wrapper = mount(Upload, {
stubs: ['file-upload']
});
wrapper.vm.inputFile();
expect(wrapper.vm.name).toBe('');
wrapper.vm.inputFile({ file: blob, name: '123.png' });
expect(wrapper.vm.name).toBe('123');
wrapper.vm.inputFile({ file: blob, name: '456.png' });
expect(wrapper.vm.name).toBe('123');
wrapper.setData({ name: '' });
wrapper.vm.inputFile({ file: blob, name: '789' });
expect(wrapper.vm.name).toBe('789');
expect(wrapper.vm.texture).toBe('file-url');
});
test('upload file', async () => {
window.Request = jest.fn();
walkFetch
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValueOnce({ errno: 0, msg: '0', tid: 1 });
jest.spyOn(toastr, 'info');
swal.mockReturnValue();
const wrapper = mount(Upload, {
stubs: ['file-upload']
});
const button = wrapper.find('.box-footer > button');
button.trigger('click');
expect(walkFetch).not.toBeCalled();
expect(toastr.info).toBeCalledWith('skinlib.emptyUploadFile');
wrapper.setData({ files: [{ file: {}, name: 't', type: 'image/jpeg' }] });
button.trigger('click');
expect(walkFetch).not.toBeCalled();
expect(toastr.info).toBeCalledWith('skinlib.emptyTextureName');
wrapper.find('[type=text]').setValue('t');
button.trigger('click');
expect(walkFetch).not.toBeCalled();
expect(toastr.info).toBeCalledWith('skinlib.fileExtError');
wrapper.setData({ files: [{ file: new Blob, name: 't.png', type: 'image/png' }] });
button.trigger('click');
await flushPromises();
expect(window.Request).toBeCalledWith('/skinlib/upload', {
body: expect.any(FormData),
credentials: 'same-origin',
headers: {
Accept: 'application/json'
},
method: 'POST'
});
expect(walkFetch).toBeCalledWith(expect.any(window.Request));
expect(swal).toBeCalledWith({ type: 'warning', text: '1' });
button.trigger('click');
await flushPromises();
jest.runAllTimers();
expect(swal).toBeCalledWith({ type: 'success', text: '0' });
expect(toastr.info).toBeCalledWith('skinlib.redirecting');
});

View File

@ -63,13 +63,22 @@ skinlib:
emptyUploadFile: You have not uploaded any file. emptyUploadFile: You have not uploaded any file.
encodingError: 'Error: Encoding of this file is not accepted.' encodingError: 'Error: Encoding of this file is not accepted.'
fileExtError: 'Error: Textures should be PNG files.' fileExtError: 'Error: Textures should be PNG files.'
upload: Upload
uploading: Uploading uploading: Uploading
redirecting: Redirecting... redirecting: Redirecting...
setAsPrivate: Set as Private setAsPrivate: Set as Private
setAsPublic: Set as Public setAsPublic: Set as Public
setPublicNotice: Sure to set this as public texture? setPublicNotice: Sure to set this as public texture?
deleteNotice: Are you sure to delete this texture? deleteNotice: Are you sure to delete this texture?
upload:
texture-name: Texture Name
texture-type: Texture Type
select-file: Select File
privacy-notice: Prevent it from being visible at skin library.
set-as-private: Make it Private
button: Upload
dropZone: Drop a file here
remove: Remove
cost: (It cost you about :score score)
user: user:
signRemainingTime: 'Available after :time :unit' signRemainingTime: 'Available after :time :unit'

View File

@ -65,13 +65,22 @@ skinlib:
emptyUploadFile: 你还没有上传任何文件哦 emptyUploadFile: 你还没有上传任何文件哦
encodingError: 错误:这张图片编码不对哦 encodingError: 错误:这张图片编码不对哦
fileExtError: 错误:皮肤文件必须为 PNG 格式 fileExtError: 错误:皮肤文件必须为 PNG 格式
upload: 确认上传
uploading: 上传中 uploading: 上传中
redirecting: 正在跳转... redirecting: 正在跳转...
setAsPrivate: 设为隐私 setAsPrivate: 设为隐私
setAsPublic: 设为公开 setAsPublic: 设为公开
setPublicNotice: 要将此材质设置为公开吗? setPublicNotice: 要将此材质设置为公开吗?
deleteNotice: 真的要删除此材质吗? deleteNotice: 真的要删除此材质吗?
upload:
texture-name: 材质名称
texture-type: 材质类型
select-file: 选择文件
privacy-notice: 其他人将不会在皮肤库中看到此材质
set-as-private: 设置为私密材质
button: 确认上传
dropZone: 拖拽文件到这里
remove: 移除
cost: (这会消耗您约 :score 积分)
user: user:
signRemainingTime: ':time :unit 后可签到' signRemainingTime: ':time :unit 后可签到'
@ -290,86 +299,6 @@ general:
last-modified: 修改时间 last-modified: 修改时间
vendor: vendor:
fileinput:
fileSingle: 文件
filePlural: 个文件
browseLabel: 选择 &hellip;
removeLabel: 移除
removeTitle: 清除选中文件
cancelLabel: 取消
cancelTitle: 取消进行中的上传
uploadLabel: 上传
uploadTitle: 上传选中文件
msgNo: 没有
msgNoFilesSelected: ''
msgCancelled: 取消
msgPlaceholder: '选择 {files}...'
msgZoomModalHeading: 详细预览
msgFileRequired: 必须选择一个文件上传.
msgSizeTooSmall: '文件 "{name}" (<b>{size} KB</b>) 必须大于限定大小 <b>{minSize} KB</b>.'
msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.'
msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. '
msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.'
msgFileNotFound: '文件 "{name}" 未找到!'
msgFileSecured: '安全限制,为了防止读取文件 "{name}".'
msgFileNotReadable: '文件 "{name}" 不可读.'
msgFilePreviewAborted: '取消 "{name}" 的预览.'
msgFilePreviewError: '读取 "{name}" 时出现了一个错误.'
msgInvalidFileName: '文件名 "{name}" 包含非法字符.'
msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.'
msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.'
msgFileTypes:
image: image
html: HTML
text: text
video: video
audio: audio
flash: flash
pdf: PDF
object: object
msgUploadAborted: 该文件上传被中止
msgUploadThreshold: 处理中...
msgUploadBegin: 正在初始化...
msgUploadEnd: 完成
msgUploadEmpty: 无效的文件上传.
msgUploadError: Error
msgValidationError: 验证错误
msgLoading: '加载第 {index} 文件 共 {files} &hellip;'
msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.'
msgSelected: '{n} {files} 选中'
msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.'
msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.'
msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.'
msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.'
msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.'
msgImageResizeError: 无法获取的图像尺寸调整。
msgImageResizeException: '调整图像大小时发生错误。<pre>{errors}</pre>'
msgAjaxError: '{operation} 发生错误. 请重试!'
msgAjaxProgressError: '{operation} 失败'
ajaxOperations:
deleteThumb: 删除文件
uploadThumb: 上传文件
uploadBatch: 批量上传
uploadExtra: 表单数据上传
dropZoneTitle: 拖拽文件到这里 &hellip;<br>支持多文件同时上传
dropZoneClickTitle: '<br>(或点击{files}按钮选择文件)'
fileActionSettings:
removeTitle: 删除文件
uploadTitle: 上传文件
uploadRetryTitle: Retry upload
zoomTitle: 查看详情
dragTitle: 移动 / 重置
indicatorNewTitle: 没有上传
indicatorSuccessTitle: 上传
indicatorErrorTitle: 上传错误
indicatorLoadingTitle: 上传 ...
previewZoomButtonTitles:
prev: 预览上一个文件
next: 预览下一个文件
toggleheader: 缩放
fullscreen: 全屏
borderless: 无边界模式
close: 关闭当前预览
datatable: datatable:
search: 搜索 search: 搜索
rowsPerPage: 每页的项目数 rowsPerPage: 每页的项目数

View File

@ -2,15 +2,6 @@
@section('title', trans('skinlib.upload.title')) @section('title', trans('skinlib.upload.title'))
@section('style')
<style>
label[for="type-skin"],
label[for="type-cape"] {
margin-top: 5px;
}
</style>
@endsection
@section('content') @section('content')
<!-- Full Width Column --> <!-- Full Width Column -->
<div class="content-wrapper"> <div class="content-wrapper">
@ -23,83 +14,16 @@
</section> </section>
<!-- Main content --> <!-- Main content -->
<section class="content"> <section class="content"></section><!-- /.content -->
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-body">
<div class="form-group">
<label for="name">@lang('skinlib.upload.texture-name')</label>
@php
$regexp = option('texture_name_regexp');
@endphp
<input id="name" class="form-control" type="text" placeholder="{{
$regexp ? trans('skinlib.upload.name-rule-regexp', compact('regexp')) : trans('skinlib.upload.name-rule')
}}" />
</div>
<div class="form-group">
<label>@lang('skinlib.upload.texture-type')</label>
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-xs-4">
<label for="type-skin">
<input type="radio" name="type" id="type-skin" checked> @lang('general.skin')
</label>
</div>
<div class="col-xs-8">
<select class="form-control" id="skin-type">
<option value="steve">@lang('skinlib.filter.steve-model')</option>
<option value="alex">@lang('skinlib.filter.alex-model')</option>
</select>
</div>
</div>
</div>
<div class="col-md-6">
<label for="type-cape">
<input type="radio" name="type" id="type-cape"> @lang('general.cape')
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="file">@lang('skinlib.upload.select-file')</label>
<input id="file" type="file" data-show-upload="false" data-language="{{ config('app.locale') }}" class="file" accept="image/png" />
</div>
<div class="callout callout-info" id="msg" style="display: none;">
<p>@lang('skinlib.upload.private-score-notice', ['score' => option('private_score_per_storage')])</p>
</div>
</div><!-- /.box-body -->
<div class="box-footer">
<label for="private" class="pull-right" title="@lang('skinlib.upload.privacy-notice')" data-placement="top" data-toggle="tooltip">
<input id="private" type="checkbox"> @lang('skinlib.upload.set-as-private')
</label>
<button id="upload-button" onclick="upload()" class="btn btn-primary">@lang('skinlib.upload.button')</button>
</div>
</div><!-- /.box -->
</div>
<div class="col-md-6">
<div class="box box-default">
@include('common.texture-preview')
</div>
</div>
</div>
</section><!-- /.content -->
</div><!-- /.container --> </div><!-- /.container -->
</div><!-- /.content-wrapper --> </div><!-- /.content-wrapper -->
@endsection
@section('script')
<script> <script>
initUploadListeners(); var __bs_data__ = {
registerAnimationController(); rule: "{{ option('texture_name_regexp') ? trans('skinlib.upload.name-rule-regexp', compact('regexp')) : trans('skinlib.upload.name-rule') }}",
registerWindowResizeHandler(); privacyNotice: "@lang('skinlib.upload.private-score-notice', ['score' => option('private_score_per_storage')])",
scorePublic: {{ option('score_per_storage') }},
scorePrivate: {{ option('private_score_per_storage') }}
}
</script> </script>
@endsection @endsection

View File

@ -16,7 +16,6 @@ const config = {
style: [ style: [
'bootstrap/dist/css/bootstrap.min.css', 'bootstrap/dist/css/bootstrap.min.css',
'admin-lte/dist/css/AdminLTE.min.css', 'admin-lte/dist/css/AdminLTE.min.css',
'bootstrap-fileinput/css/fileinput.min.css',
'@fortawesome/fontawesome-free/css/fontawesome.min.css', '@fortawesome/fontawesome-free/css/fontawesome.min.css',
'@fortawesome/fontawesome-free/css/regular.min.css', '@fortawesome/fontawesome-free/css/regular.min.css',
'@fortawesome/fontawesome-free/css/solid.min.css', '@fortawesome/fontawesome-free/css/solid.min.css',

View File

@ -1535,13 +1535,6 @@ bootstrap-daterangepicker@^2.1.25:
jquery ">=1.10" jquery ">=1.10"
moment "^2.9.0" moment "^2.9.0"
bootstrap-fileinput@^4.4.7:
version "4.4.7"
resolved "https://registry.yarnpkg.com/bootstrap-fileinput/-/bootstrap-fileinput-4.4.7.tgz#ea6df7f6919e8de777ab7900fd07eed0893163ef"
dependencies:
bootstrap ">= 3.0.0"
jquery ">= 1.9.0"
bootstrap-slider@^9.8.0: bootstrap-slider@^9.8.0:
version "9.10.0" version "9.10.0"
resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-9.10.0.tgz#1103d6bc00cfbfa8cfc9a2599ab518c55643da3f" resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-9.10.0.tgz#1103d6bc00cfbfa8cfc9a2599ab518c55643da3f"
@ -1550,10 +1543,6 @@ bootstrap-timepicker@^0.5.2:
version "0.5.2" version "0.5.2"
resolved "https://registry.yarnpkg.com/bootstrap-timepicker/-/bootstrap-timepicker-0.5.2.tgz#10ed9f2a2f0b8ccaefcde0fcf6a0738b919a3835" resolved "https://registry.yarnpkg.com/bootstrap-timepicker/-/bootstrap-timepicker-0.5.2.tgz#10ed9f2a2f0b8ccaefcde0fcf6a0738b919a3835"
"bootstrap@>= 3.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a"
bootstrap@^3.3.7: bootstrap@^3.3.7:
version "3.3.7" version "3.3.7"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71" resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
@ -4978,7 +4967,7 @@ jquery-ui@^1.12.1:
version "1.12.1" version "1.12.1"
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51"
"jquery@2 - 3", "jquery@>= 1.9.0", jquery@>=1.10, jquery@>=1.12.0, jquery@>=1.5, jquery@>=1.7, "jquery@>=1.7.1 <4.0.0", jquery@>=1.8, jquery@^3.2.1, jquery@^3.3.1: "jquery@2 - 3", jquery@>=1.10, jquery@>=1.12.0, jquery@>=1.5, jquery@>=1.7, "jquery@>=1.7.1 <4.0.0", jquery@>=1.8, jquery@^3.2.1, jquery@^3.3.1:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
@ -8481,6 +8470,10 @@ vue-template-es2015-compiler@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
vue-upload-component@^2.8.11:
version "2.8.11"
resolved "https://registry.npmjs.org/vue-upload-component/-/vue-upload-component-2.8.11.tgz#7dd207144ba9502760aecf77d42e4ab072b94b07"
vue@^2.5.16: vue@^2.5.16:
version "2.5.16" version "2.5.16"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.16.tgz#07edb75e8412aaeed871ebafa99f4672584a0085" resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.16.tgz#07edb75e8412aaeed871ebafa99f4672584a0085"