From 733e694ba0dc40a033f33e1d24cd0f888f81304c Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Wed, 15 Aug 2018 16:44:21 +0800 Subject: [PATCH] Add "upload" page --- package.json | 2 +- resources/assets/src/components/route.js | 5 + .../assets/src/components/skinlib/Upload.vue | 222 ++++++++++++++++++ resources/assets/src/js/layout.js | 1 - resources/assets/src/js/net.js | 2 +- .../tests/components/skinlib/Upload.test.js | 138 +++++++++++ resources/lang/en/front-end.yml | 11 +- resources/lang/zh_CN/front-end.yml | 91 +------ resources/views/skinlib/upload.tpl | 90 +------ webpack.config.js | 1 - yarn.lock | 17 +- 11 files changed, 399 insertions(+), 181 deletions(-) create mode 100644 resources/assets/src/components/skinlib/Upload.vue create mode 100644 resources/assets/tests/components/skinlib/Upload.test.js diff --git a/package.json b/package.json index 41e56226..90fffc52 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "@fortawesome/fontawesome-free": "^5.2.0", "admin-lte": "^2.4.2", "bootstrap": "^3.3.7", - "bootstrap-fileinput": "^4.4.7", "chart.js": "^2.7.1", "es6-promise": "^4.2.4", "highlight.js": "^9.12.0", @@ -36,6 +35,7 @@ "toastr": "^2.1.4", "vue": "^2.5.16", "vue-good-table": "^2.12.2", + "vue-upload-component": "^2.8.11", "vuejs-paginate": "^2.0.1", "whatwg-fetch": "^2.0.4" }, diff --git a/resources/assets/src/components/route.js b/resources/assets/src/components/route.js index 99967821..8054a370 100644 --- a/resources/assets/src/components/route.js +++ b/resources/assets/src/components/route.js @@ -59,4 +59,9 @@ export default [ component: () => import('./skinlib/List'), el: '.content-wrapper' }, + { + path: 'skinlib/upload', + component: () => import('./skinlib/Upload'), + el: '.content' + }, ]; diff --git a/resources/assets/src/components/skinlib/Upload.vue b/resources/assets/src/components/skinlib/Upload.vue new file mode 100644 index 00000000..55c7bde5 --- /dev/null +++ b/resources/assets/src/components/skinlib/Upload.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/resources/assets/src/js/layout.js b/resources/assets/src/js/layout.js index 5f65ff2d..c4b8c8ea 100644 --- a/resources/assets/src/js/layout.js +++ b/resources/assets/src/js/layout.js @@ -1,6 +1,5 @@ import './jquery'; // jQuery first import 'bootstrap'; -import 'bootstrap-fileinput'; import 'admin-lte'; import 'icheck'; import Vue from 'vue'; diff --git a/resources/assets/src/js/net.js b/resources/assets/src/js/net.js index 69e169a9..409aa713 100644 --- a/resources/assets/src/js/net.js +++ b/resources/assets/src/js/net.js @@ -10,7 +10,7 @@ const init = { } }; -async function walkFetch(request) { +export async function walkFetch(request) { try { const response = await fetch(request); if (response.ok) { diff --git a/resources/assets/tests/components/skinlib/Upload.test.js b/resources/assets/tests/components/skinlib/Upload.test.js new file mode 100644 index 00000000..8564861b --- /dev/null +++ b/resources/assets/tests/components/skinlib/Upload.test.js @@ -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'); +}); diff --git a/resources/lang/en/front-end.yml b/resources/lang/en/front-end.yml index fcfacd3e..28e2ebcd 100644 --- a/resources/lang/en/front-end.yml +++ b/resources/lang/en/front-end.yml @@ -63,13 +63,22 @@ skinlib: emptyUploadFile: You have not uploaded any file. encodingError: 'Error: Encoding of this file is not accepted.' fileExtError: 'Error: Textures should be PNG files.' - upload: Upload uploading: Uploading redirecting: Redirecting... setAsPrivate: Set as Private setAsPublic: Set as Public setPublicNotice: Sure to set this as public 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: signRemainingTime: 'Available after :time :unit' diff --git a/resources/lang/zh_CN/front-end.yml b/resources/lang/zh_CN/front-end.yml index 8b7c78d3..cd41ed88 100644 --- a/resources/lang/zh_CN/front-end.yml +++ b/resources/lang/zh_CN/front-end.yml @@ -65,13 +65,22 @@ skinlib: emptyUploadFile: 你还没有上传任何文件哦 encodingError: 错误:这张图片编码不对哦 fileExtError: 错误:皮肤文件必须为 PNG 格式 - upload: 确认上传 uploading: 上传中 redirecting: 正在跳转... setAsPrivate: 设为隐私 setAsPublic: 设为公开 setPublicNotice: 要将此材质设置为公开吗? deleteNotice: 真的要删除此材质吗? + upload: + texture-name: 材质名称 + texture-type: 材质类型 + select-file: 选择文件 + privacy-notice: 其他人将不会在皮肤库中看到此材质 + set-as-private: 设置为私密材质 + button: 确认上传 + dropZone: 拖拽文件到这里 + remove: 移除 + cost: (这会消耗您约 :score 积分) user: signRemainingTime: ':time :unit 后可签到' @@ -290,86 +299,6 @@ general: last-modified: 修改时间 vendor: - fileinput: - fileSingle: 文件 - filePlural: 个文件 - browseLabel: 选择 … - removeLabel: 移除 - removeTitle: 清除选中文件 - cancelLabel: 取消 - cancelTitle: 取消进行中的上传 - uploadLabel: 上传 - uploadTitle: 上传选中文件 - msgNo: 没有 - msgNoFilesSelected: '' - msgCancelled: 取消 - msgPlaceholder: '选择 {files}...' - msgZoomModalHeading: 详细预览 - msgFileRequired: 必须选择一个文件上传. - msgSizeTooSmall: '文件 "{name}" ({size} KB) 必须大于限定大小 {minSize} KB.' - msgSizeTooLarge: '文件 "{name}" ({size} KB) 超过了允许大小 {maxSize} KB.' - msgFilesTooLess: '你必须选择最少 {n} {files} 来上传. ' - msgFilesTooMany: '选择的上传文件个数 ({n}) 超出最大文件的限制个数 {m}.' - 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} …' - msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.' - msgSelected: '{n} {files} 选中' - msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.' - msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.' - msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.' - msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.' - msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.' - msgImageResizeError: 无法获取的图像尺寸调整。 - msgImageResizeException: '调整图像大小时发生错误。
{errors}
' - msgAjaxError: '{operation} 发生错误. 请重试!' - msgAjaxProgressError: '{operation} 失败' - ajaxOperations: - deleteThumb: 删除文件 - uploadThumb: 上传文件 - uploadBatch: 批量上传 - uploadExtra: 表单数据上传 - dropZoneTitle: 拖拽文件到这里 …
支持多文件同时上传 - dropZoneClickTitle: '
(或点击{files}按钮选择文件)' - fileActionSettings: - removeTitle: 删除文件 - uploadTitle: 上传文件 - uploadRetryTitle: Retry upload - zoomTitle: 查看详情 - dragTitle: 移动 / 重置 - indicatorNewTitle: 没有上传 - indicatorSuccessTitle: 上传 - indicatorErrorTitle: 上传错误 - indicatorLoadingTitle: 上传 ... - previewZoomButtonTitles: - prev: 预览上一个文件 - next: 预览下一个文件 - toggleheader: 缩放 - fullscreen: 全屏 - borderless: 无边界模式 - close: 关闭当前预览 datatable: search: 搜索 rowsPerPage: 每页的项目数 diff --git a/resources/views/skinlib/upload.tpl b/resources/views/skinlib/upload.tpl index 8ae88d4f..be764bf8 100644 --- a/resources/views/skinlib/upload.tpl +++ b/resources/views/skinlib/upload.tpl @@ -2,15 +2,6 @@ @section('title', trans('skinlib.upload.title')) -@section('style') - -@endsection - @section('content')
@@ -23,83 +14,16 @@ -
-
-
-
-
-
- - @php - $regexp = option('texture_name_regexp'); - @endphp - -
- -
- -
-
-
-
- -
-
- -
-
-
- -
- -
-
- -
- -
- - -
- - -
- - -
-
-
-
- @include('common.texture-preview') -
-
-
- -
+
-@endsection -@section('script') @endsection diff --git a/webpack.config.js b/webpack.config.js index c020a393..93029c4e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -16,7 +16,6 @@ const config = { style: [ 'bootstrap/dist/css/bootstrap.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/regular.min.css', '@fortawesome/fontawesome-free/css/solid.min.css', diff --git a/yarn.lock b/yarn.lock index 9ed461c9..d6fcde26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1535,13 +1535,6 @@ bootstrap-daterangepicker@^2.1.25: jquery ">=1.10" 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: version "9.10.0" 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" 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: version "3.3.7" 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" 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" 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" 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: version "2.5.16" resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.16.tgz#07edb75e8412aaeed871ebafa99f4672584a0085"