From f53bb7acb6bf5a81553bbf4e33006a476db0ddc4 Mon Sep 17 00:00:00 2001 From: gplane Date: Sun, 23 Apr 2017 23:58:22 +0800 Subject: [PATCH] Optimize closet 1. Use jqPaginator 2. Use CSR for closet 3. Use AJAX for closet 4. Just type to search instead of pressing ENTER key 5. Link to skin library is according to current category when closet is empty 6. Texture indicator shows category of texture --- app/Http/Controllers/ClosetController.php | 41 ++++--- app/Http/Routes/web.php | 1 + bower.json | 7 +- gulpfile.js | 2 + resources/assets/src/scripts/user.js | 130 ++++++++++++++++++++-- resources/lang/en/locale.js | 11 +- resources/lang/en/user.yml | 4 +- resources/lang/zh_CN/locale.js | 11 +- resources/lang/zh_CN/user.yml | 4 +- resources/views/user/closet.tpl | 43 ++----- resources/views/vendor/closet-items.tpl | 30 ----- 11 files changed, 180 insertions(+), 104 deletions(-) delete mode 100644 resources/views/vendor/closet-items.tpl diff --git a/app/Http/Controllers/ClosetController.php b/app/Http/Controllers/ClosetController.php index 5f07b80b..7e35742c 100644 --- a/app/Http/Controllers/ClosetController.php +++ b/app/Http/Controllers/ClosetController.php @@ -25,43 +25,40 @@ class ClosetController extends Controller $this->closet = new Closet(session('uid')); } - public function index(Request $request) + public function index() + { + return view('user.closet')->with('user', app('user.current')); + } + + public function getClosetData(Request $request) { $category = $request->input('category', 'skin'); - $page = $request->input('page', 1); - $page = $page <= 0 ? 1 : $page; + $page = abs($request->input('page', 1)); $q = $request->input('q', null); $items = []; if ($q) { - foreach (['skin', 'cape'] as $category) { - // do search - foreach ($this->closet->getItems($category) as $item) { - if (strstr($item->name, $q)) { - $items[$category][] = $item; - } + // do search + foreach ($this->closet->getItems($category) as $item) { + if (stristr($item->name, $q)) { + $items[] = $item; } } } else { - $items['skin'] = $this->closet->getItems('skin'); - $items['cape'] = $this->closet->getItems('cape'); + $items = $this->closet->getItems($category); } // pagination - $total_pages = []; + $total_pages = ceil(count($items) / 6); - foreach ($items as $key => $value) { - $total_pages[] = ceil(count($items[$key]) / 6); - $items[$key] = array_slice($value, ($page-1)*6, 6); - } + $items = array_slice($items, ($page - 1) * 6, 6); - return view('user.closet')->with('items', $items) - ->with('page', $page) - ->with('q', $q) - ->with('category', $category) - ->with('total_pages', $total_pages ? max($total_pages) : 0) - ->with('user', app('user.current')); + return response()->json([ + 'category' => $category, + 'items' => $items, + 'total_pages' => $total_pages + ]); } public function info() diff --git a/app/Http/Routes/web.php b/app/Http/Routes/web.php index b512ae65..d2b2dad1 100644 --- a/app/Http/Routes/web.php +++ b/app/Http/Routes/web.php @@ -65,6 +65,7 @@ Route::group(['middleware' => 'auth', 'prefix' => 'user'], function () // Closet Route::get ('/closet', 'ClosetController@index'); + Route::get ('/closet-data', 'ClosetController@getClosetData'); Route::post('/closet/add', 'ClosetController@add'); Route::post('/closet/remove', 'ClosetController@remove'); Route::post('/closet/rename', 'ClosetController@rename'); diff --git a/bower.json b/bower.json index 867479f8..c4771d4e 100644 --- a/bower.json +++ b/bower.json @@ -24,6 +24,11 @@ "font-awesome": "Font-Awesome#^4.6.3", "bootstrap-fileinput": "^4.3.3", "bootstrap": "^3.3.6", - "sweetalert2": "^4.1.6" + "sweetalert2": "^4.1.6", + "jqPaginator": "^1.2.0", + "lodash": "^4.17.4" + }, + "resolutions": { + "jquery": "1.9.1 - 3" } } diff --git a/gulpfile.js b/gulpfile.js index d1a8c8af..836178e9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,6 +25,7 @@ var srcPath = 'resources/assets/src/'; var distPath = 'resources/assets/dist/'; var vendorScripts = [ + 'lodash/dist/lodash.min.js', 'jquery/dist/jquery.min.js', 'bootstrap/dist/js/bootstrap.min.js', 'AdminLTE/dist/js/app.min.js', @@ -35,6 +36,7 @@ var vendorScripts = [ 'toastr/toastr.min.js', 'es6-promise/es6-promise.auto.min.js', 'sweetalert2/dist/sweetalert2.min.js', + 'jqPaginator/dist/jqPaginator.min.js', 'resources/assets/dist/scripts/general.js', ]; diff --git a/resources/assets/src/scripts/user.js b/resources/assets/src/scripts/user.js index 9ed9685e..545873d2 100644 --- a/resources/assets/src/scripts/user.js +++ b/resources/assets/src/scripts/user.js @@ -18,13 +18,13 @@ $('body').on('click', '#preview-switch', () => { TexturePreview.previewType == '3D' ? TexturePreview.show2dPreview() : TexturePreview.show3dPreview(); }); -var selected = []; +let skinSelected = false, capeSelected = false; $('body').on('click', '.item-body', function () { $('.item-selected').parent().removeClass('item-selected'); $(this).parent().addClass('item-selected'); - let tid = $(this).parent().attr('tid'); + const tid = $(this).parent().attr('tid'); $.ajax({ type: "POST", @@ -33,24 +33,95 @@ $('body').on('click', '.item-body', function () { success: (json) => { if (json.type == "cape") { MSP.changeCape('../textures/' + json.hash); - selected['cape'] = tid; + capeSelected = true; } else { MSP.changeSkin('../textures/' + json.hash); - selected['skin'] = tid; + skinSelected = true; } - selected.length = 0; - - ['skin', 'cape'].forEach((key) => { - if (selected[key] !== undefined) selected.length++; - - $('#textures-indicator').html(selected.length); - }); + if (skinSelected && capeSelected) + $('#textures-indicator').text(`${trans('general.skin')} & ${trans('general.cape')}`); + else if (skinSelected) + $('#textures-indicator').text(trans('general.skin')); + else if (capeSelected) + $('#textures-indicator').text(trans('general.cape')); }, error: showAjaxError }); }); +$('body').on('click', '.category-switch', () => { + const category = $('a[href="#skin-category"]').parent().hasClass('active') ? 'cape' : 'skin'; + const page = parseInt($('#closet-paginator').attr(`last-${category}-page`)); + const search = $('input[name=q]').val(); + reloadCloset(category, page, search); +}); + +function renderClosetItemComponent(item, rootPath) { + return ` +
+
+ +
+ +
`; +} + +function renderCloset(items, category) { + const rootPath = /(^https?.*)\/user\/closet/.exec(window.location.href)[1]; + const search = $('input[name=q]').val(); + let container = $(`#${category}-category`); + container.html(''); + if (items.length === 0) { + $('#closet-paginator').hide(); + if (search === '') { + container.html(`
+ ${trans('user.emptyClosetMsg', { url: rootPath + '/skinlib?filter=' + category })}
`); + } else { + container.html(`
${trans('general.noResult')}
`); + } + } else { + $('#closet-paginator').show(); + for (const item of items) { + container.append(renderClosetItemComponent(item, rootPath)); + } + } +} + +function reloadCloset(category, page, search) { + Promise.resolve($.ajax({ + type: 'GET', + url: /(^https?.*)\/user\/closet/.exec(window.location.href)[1] + '/user/closet-data', + dataType: 'json', + data: { + category: category, + page: page, + q: search + } + })).then(result => { + renderCloset(result.items, result.category); + let paginator = $('#closet-paginator'); + paginator.attr(`last-${result.category}-page`, page); + paginator.jqPaginator('option', { + currentPage: page, + totalPages: result.total_pages + }); + }).catch(error => showAjaxError); +} + function showPlayerTexturePreview(pid) { $.ajax({ type: "POST", @@ -179,6 +250,43 @@ $(document).ready(function() { confirmButtonText: trans('general.confirm'), cancelButtonText: trans('general.cancel') }); + + if (window.location.pathname.includes('/user/closet')) { + Promise.resolve($.ajax({ + type: 'GET', + url: /(^https?.*)\/user\/closet/.exec(window.location.href)[1] + '/user/closet-data', + dataType: 'json' + })).then(result => { + renderCloset(result.items, result.category); + $('#closet-paginator').jqPaginator({ + totalPages: result.total_pages, + visiblePages: 5, + currentPage: 1, + first: '
  • «
  • ', + prev: '
  • ', + next: '
  • ', + last: '
  • »
  • ', + page: '
  • {{page}}
  • ', + wrapper: '', + onPageChange: page => { + reloadCloset( + $('#skin-category').hasClass('active') ? 'skin' : 'cape', + page, + $('input[name=q]').val() + ); + } + }); + }).catch(error => showAjaxError); + + $('input[name=q]').on('input', _.debounce(() => { + const category = $('#skin-category').hasClass('active') ? 'skin' : 'cape'; + reloadCloset( + category, + 1, + $('input[name=q]').val() + ); + }, 350)); + } }); function setTexture() { diff --git a/resources/lang/en/locale.js b/resources/lang/en/locale.js index ae4d7f62..80fe4ac5 100644 --- a/resources/lang/en/locale.js +++ b/resources/lang/en/locale.js @@ -68,6 +68,11 @@ signInRemainingTime: 'Available after :time hours', // Closet + emptyClosetMsg: '

    Nothing in your closet...

    Why not explore the Skin Library for a while?

    ', + renameItem: 'Rename item', + removeItem: 'Remove from closet', + setAsAvatar: 'Set as avatar', + viewInSkinlib: 'View in skin library', switch2dPreview: 'Switch to 2D Preview', switch3dPreview: 'Switch to 3D Preview', removeFromClosetNotice: 'Sure to remove this texture from your closet?', @@ -153,10 +158,14 @@ extracting: 'Extracting update package..' }, general: { + skin: 'Skin', + cape: 'Cape', fatalError: 'Fatal Error (Please contact the author)', confirmLogout: 'Sure to log out?', confirm: 'OK', - cancel: 'Cancel' + cancel: 'Cancel', + more: 'More', + noResult: 'No result.' } }; })(window.jQuery); diff --git a/resources/lang/en/user.yml b/resources/lang/en/user.yml index a9b019df..761d28e6 100644 --- a/resources/lang/en/user.yml +++ b/resources/lang/en/user.yml @@ -28,13 +28,11 @@ score-intro: closet: upload: Upload Texture search: Search Texture + type-to-search: Type to search switch-category: Switch Category view: View in skin library more: More set-avatar: Set as avatar - empty-msg: | -

    Nothing in your closet...

    -

    Why not explore the Skin Library for a while?

    use-as: button: Apply... diff --git a/resources/lang/zh_CN/locale.js b/resources/lang/zh_CN/locale.js index d68cb06b..4a9fa546 100644 --- a/resources/lang/zh_CN/locale.js +++ b/resources/lang/zh_CN/locale.js @@ -68,6 +68,11 @@ signInRemainingTime: ':time 小时后可签到', // Closet + emptyClosetMsg: '

    衣柜里啥都没有哦~

    皮肤库看看吧~

    ', + renameItem: '重命名物品', + removeItem: '从衣柜中移除', + setAsAvatar: '设为头像', + viewInSkinlib: '在皮肤库中查看', switch2dPreview: '切换 2D 预览', switch3dPreview: '切换 3D 预览', removeFromClosetNotice: '确定要从衣柜中移除此材质吗?', @@ -153,10 +158,14 @@ extracting: '正在解压更新包' }, general: { + skin: '皮肤', + cape: '披风', fatalError: '严重错误(请联系作者)', confirmLogout: '确定要登出吗?', confirm: '确定', - cancel: '取消' + cancel: '取消', + more: '更多', + noResult: '无结果' }, vendor: { datatables: { diff --git a/resources/lang/zh_CN/user.yml b/resources/lang/zh_CN/user.yml index ad7b9861..19e8ffc7 100644 --- a/resources/lang/zh_CN/user.yml +++ b/resources/lang/zh_CN/user.yml @@ -35,13 +35,11 @@ score-intro: closet: upload: 上传材质 search: 搜索材质 + type-to-search: 输入即搜索 switch-category: 切换分类 view: 在皮肤库中查看 more: 更多 set-avatar: 设为头像 - empty-msg: | -

    衣柜里啥都没有哦~

    -

    皮肤库看看吧~

    use-as: button: 使用... diff --git a/resources/views/user/closet.tpl b/resources/views/user/closet.tpl index 219e1426..993fbaa6 100644 --- a/resources/views/user/closet.tpl +++ b/resources/views/user/closet.tpl @@ -26,51 +26,30 @@ - +
    diff --git a/resources/views/vendor/closet-items.tpl b/resources/views/vendor/closet-items.tpl deleted file mode 100644 index 6f0f4bf4..00000000 --- a/resources/views/vendor/closet-items.tpl +++ /dev/null @@ -1,30 +0,0 @@ -@forelse ($items as $item) -
    -
    - -
    - -
    -@empty -
    - @if($q) - {{ trans('skinlib.general.no-result') }} - @else - {!! trans('user.closet.empty-msg', ['url' => url('skinlib')]) !!} - @endif -
    - -@endforelse