Add index page of skin library

This commit is contained in:
Pig Fang 2018-08-14 23:27:36 +08:00
parent 7e4ae5381b
commit 190e54578a
12 changed files with 806 additions and 112 deletions

View File

@ -56,9 +56,6 @@ class SkinlibController extends Controller
// Keyword to search
$keyword = $request->input('keyword', '');
// Check if user logged in
$anonymous = !Auth::check();
if ($filter == "skin") {
$query = Texture::where(function ($innerQuery) {
// Nested condition, DO NOT MODIFY
@ -76,7 +73,7 @@ class SkinlibController extends Controller
$query = $query->where('uploader', $uploader);
}
if ($anonymous) {
if (! $currentUser) {
// Show public textures only to anonymous visitors
$query = $query->where('public', true);
} else {
@ -95,7 +92,7 @@ class SkinlibController extends Controller
->take($itemsPerPage)
->get();
if (! $anonymous) {
if ($currentUser) {
$closet = new Closet($currentUser->uid);
foreach ($textures as $item) {
$item->liked = $closet->has($item->tid);
@ -104,7 +101,7 @@ class SkinlibController extends Controller
return response()->json([
'items' => $textures,
'anonymous' => $anonymous,
'current_uid' => $currentUser ? $currentUser->uid : 0,
'total_pages' => $totalPages
]);
}

View File

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

View File

@ -0,0 +1,272 @@
<template>
<div class="content-wrapper">
<div class="container">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>{{ $t('general.skinlib') }}</h1>
<ol class="breadcrumb">
<li><i class="fas fa-tags"></i> {{ $t('skinlib.nowShowing') }}</li>
<li>
<span v-if="filter === 'cape'" v-t="'general.cape'"></span>
<span v-else>
{{ $t('general.skin') }}
{{ $t('skinlib.filter.' + filter) }}
</span>
</li>
<li>{{ uploaderIndicator }}</li>
<li class="active">{{ sortIndicator }}</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<div class="box box-default">
<div class="box-header">
<div class="form-group row">
<div class="col-md-4">
<label>
<input
type="radio"
name="filter"
value="skin"
v-model="filter"
>
{{ $t('skinlib.anyModels') }}
</label>&nbsp;
<label>
<input
type="radio"
name="filter"
value="steve"
v-model="filter"
>
Steve
</label>&nbsp;
<label>
<input
type="radio"
name="filter"
value="alex"
v-model="filter"
>
Alex
</label>&nbsp;
<label>
<input
type="radio"
name="filter"
value="cape"
v-model="filter"
>
{{ $t('general.cape') }}
</label>
</div>
<form class="col-md-4" @submit.prevent="fetchData">
<div class="input-group">
<input
type="text"
v-model="keyword"
:placeholder="$t('vendor.datatable.search')"
class="form-control"
>
<span class="input-group-btn">
<button @click="fetchData" class="btn btn-success">
{{ $t('general.submit') }}
</button>
</span>
</div>
</form>
<div class="col-md-4">
<div class="btn-group filter-btn">
<button
class="btn btn-primary dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>{{ $t('skinlib.sort.title') }} <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a @click="sort = 'likes'" v-t="'skinlib.sort.likes'" href="#"></a></li>
<li><a @click="sort = 'time'" v-t="'skinlib.sort.time'" href="#"></a></li>
</ul>
</div>
<a
v-if="currentUid"
class="btn btn-default btn-tail"
@click="uploader = currentUid"
>{{ $t('skinlib.seeMyUpload') }}</a>
<button class="btn btn-warning btn-tail" @click="reset">
{{ $t('skinlib.reset') }}
</button>
</div>
</div>
</div>
<!-- Container of Skin Library -->
<div class="box-body">
<template v-if="items.length">
<skin-lib-item
v-for="(item, index) in items"
:key="item.tid"
:tid="item.tid"
:name="item.name"
:type="item.type"
:liked="item.liked"
:isPublic="item.public"
:anonymous="anonymous"
@like-toggled="onLikeToggled(index, $event)"
></skin-lib-item>
</template>
<p v-else style="text-align: center; margin: 30px 0;">
{{ $t('general.noResult') }}
</p>
</div>
<div class="box-footer">
<paginate
v-model="page"
:page-count="totalPages"
class="pull-right"
container-class="pagination pagination-sm no-margin"
first-button-text="«"
prev-text=""
next-text=""
last-button-text="»"
:click-handler="pageChanged"
:first-last-button="true"
></paginate>
</div>
<div v-show="pending" class="overlay">
<span>
<i class="fas fa-sync-alt fa-spin"></i>
{{ $t('general.loading') }}
</span>
</div>
</div><!-- /.box -->
</section><!-- /.content -->
</div><!-- /.container -->
</div><!-- /.content-wrapper -->
</template>
<script>
import Paginate from 'vuejs-paginate';
import SkinLibItem from './SkinLibItem';
import { queryString } from '../../js/utils';
export default {
name: 'SkinLibrary',
components: {
Paginate,
SkinLibItem
},
data() {
return {
filter: 'skin',
uploader: +queryString('uploader', 0),
sort: 'time',
keyword: '',
page: 1,
items: [],
totalPages: 0,
currentUid: 0,
pending: false,
};
},
computed: {
anonymous() {
return !this.currentUid;
},
uploaderIndicator() {
return this.uploader
? this.$t('skinlib.filter.uploader', { uid: this.uploader })
: this.$t('skinlib.filter.allUsers');
},
sortIndicator() {
return this.$t('skinlib.sort.' + this.sort);
}
},
watch: {
filter(value) {
$(`[value="${value}"]`).iCheck('check');
this.fetchData();
},
uploader() {
this.fetchData();
},
sort() {
this.fetchData();
}
},
created() {
let available = true;
const origin = this.fetchData;
this.fetchData = () => {
if (available) {
available = false;
setTimeout(() => available = true, 50);
origin();
}
};
},
beforeMount() {
this.fetchData();
},
methods: {
async fetchData() {
this.pending = true;
const { items, total_pages, current_uid } = await this.$http.get(
'/skinlib/data',
{
filter: this.filter,
uploader: this.uploader,
sort: this.sort,
keyword: this.keyword,
page: this.page
}
);
this.items = items;
this.totalPages = total_pages;
this.currentUid = current_uid;
this.pending = false;
},
pageChanged(page) {
this.page = page;
this.fetchData();
},
reset() {
this.filter = 'skin';
this.uploader = 0;
this.sort = 'time';
this.keyword = '';
this.page = 1;
},
onLikeToggled(index, action) {
this.items[index].liked = action;
}
}
};
</script>
<style lang="stylus">
.overlay span {
position: absolute;
top: 50%;
left: 50%;
margin-left: -40px;
margin-top: 25px;
color: #000;
font-size: 20px;
}
@media (min-width: 768px) {
.filter-btn {
margin-left: 25px;
}
}
.btn-tail {
margin-left: 6px;
}
</style>

View File

@ -0,0 +1,161 @@
<template>
<a :href="urlToDetail">
<div class="item">
<div class="item-body">
<img :src="urlToPreview">
</div>
<div class="item-footer">
<p class="texture-name">
<span :title="name">{{ name }}
<small>{{ $t('skinlib.filter.' + type) }}</small>
</span>
</p>
<a
:title="likeActionText"
class="more like"
:class="{ liked, anonymous }"
href="#"
@click.stop="toggleLiked"
><i class="fas fa-heart"></i></a>
<small v-if="!isPublic" class="more private-label">
{{ $t('skinlib.private') }}
</small>
</div>
</div>
</a>
</template>
<script>
import { swal } from '../../js/notify';
import toastr from 'toastr';
export default {
name: 'SkinLibItem',
props: {
tid: Number,
name: String,
type: {
validator: value => ['steve', 'alex', 'cape'].includes(value)
},
liked: Boolean,
anonymous: Boolean,
isPublic: Boolean // `public` is a reserved keyword
},
computed: {
urlToDetail() {
return `${blessing.base_url}/skinlib/show/${this.tid}`;
},
urlToPreview() {
return `${blessing.base_url}/preview/${this.tid}.png`;
},
likeActionText() {
if (this.anonymous) return this.$t('skinlib.anonymous');
return this.liked
? this.$t('skinlib.removeFromCloset')
: this.$t('skinlib.addToCloset');
}
},
methods: {
toggleLiked() {
if (this.anonymous) {
return;
}
if (this.liked) {
this.removeFromCloset();
} else {
this.addToCloset();
}
},
async addToCloset() {
const { dismiss, value } = await swal({
title: this.$t('skinlib.setItemName'),
inputValue: this.name,
input: 'text',
showCancelButton: true,
inputValidator: value => !value && this.$t('skinlib.emptyItemName')
});
if (dismiss) {
return;
}
const { errno, msg } = await this.$http.post(
'/user/closet/add',
{ tid: this.tid, name: value }
);
if (errno === 0) {
swal({ type: 'success', text: msg });
this.$emit('like-toggled', true);
} else {
toastr.warning(msg);
}
},
async removeFromCloset() {
const { dismiss } = await swal({
text: this.$t('user.removeFromClosetNotice'),
type: 'warning',
showCancelButton: true,
cancelButtonColor: '#3085d6',
confirmButtonColor: '#d33'
});
if (dismiss) {
return;
}
const { errno, msg } = await this.$http.post(
'/user/closet/remove',
{ tid: this.tid }
);
if (errno === 0) {
this.$emit('like-toggled', false);
swal({ type: 'success', text: msg });
} else {
toastr.warning(msg);
}
}
}
};
</script>
<style lang="stylus">
.texture-name {
width: 65%;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (min-width: 1200px) {
.item {
width: 250px;
}
.item-body > img {
margin-left: 50px;
}
.texture-name {
width: 65%;
}
}
.item-footer {
a {
color: #fff;
}
.like:hover,
.liked {
color: #e0353b;
}
}
.private-label {
margin-top: 2px;
}
</style>

View File

@ -9,3 +9,20 @@ export function debounce(func, delay) {
timer = setTimeout(func, delay);
};
}
/**
* Get parameters in query string with key.
*
* @param {string} key
* @param {string} defaultValue
* @return {string}
*/
export function queryString(key, defaultValue) {
const result = location.search.match(new RegExp('[?&]'+key+'=([^&]+)', 'i'));
if (result === null || result.length < 1){
return defaultValue;
} else {
return result[1];
}
}

View File

@ -0,0 +1,181 @@
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import List from '@/components/skinlib/List';
test('fetch data before mounting', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
mount(List);
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 0, sort: 'time', keyword: '', page: 1 }
);
});
test('empty skin library', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
const wrapper = mount(List);
expect(wrapper.text()).toContain('general.noResult');
});
test('toggle texture type', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
window.$ = jest.fn(() => ({
iCheck() {}
}));
const wrapper = mount(List);
const breadcrumb = wrapper.find('.breadcrumb');
jest.runAllTimers();
expect(breadcrumb.text()).toContain('skinlib.filter.skin');
wrapper.find('[value=steve]').setChecked();
jest.runAllTimers();
expect(breadcrumb.text()).toContain('skinlib.filter.steve');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'steve', uploader: 0, sort: 'time', keyword: '', page: 1 }
);
wrapper.find('[value=alex]').setChecked();
jest.runAllTimers();
expect(breadcrumb.text()).toContain('skinlib.filter.alex');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'alex', uploader: 0, sort: 'time', keyword: '', page: 1 }
);
wrapper.find('[value=cape]').setChecked();
jest.runAllTimers();
expect(breadcrumb.text()).toContain('general.cape');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'cape', uploader: 0, sort: 'time', keyword: '', page: 1 }
);
});
test('check specified uploader', async () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 1
});
const wrapper = mount(List);
await wrapper.vm.$nextTick();
const breadcrumb = wrapper.find('.breadcrumb');
const button = wrapper.find('.btn-default');
jest.runAllTimers();
expect(breadcrumb.text()).toContain('skinlib.filter.allUsers');
button.trigger('click');
expect(breadcrumb.text()).toContain('skinlib.filter.uploader');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 1, sort: 'time', keyword: '', page: 1 }
);
});
test('sort items', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
const wrapper = mount(List);
const sortByLikes = wrapper.find('.dropdown-menu > li:nth-child(1) > a');
const sortByTime = wrapper.find('.dropdown-menu > li:nth-child(2) > a');
jest.runAllTimers();
sortByLikes.trigger('click');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 0, sort: 'likes', keyword: '', page: 1 }
);
expect(wrapper.text()).toContain('skinlib.sort.likes');
jest.runAllTimers();
sortByTime.trigger('click');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 0, sort: 'time', keyword: '', page: 1 }
);
expect(wrapper.text()).toContain('skinlib.sort.time');
});
test('search by keyword', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
const wrapper = mount(List);
const searchBox = wrapper.find('input[type=text]');
jest.runAllTimers();
searchBox.setValue('a');
wrapper.find('form').trigger('submit');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 0, sort: 'time', keyword: 'a', page: 1 }
);
jest.runAllTimers();
searchBox.setValue('b');
wrapper.find('.input-group-btn > button').trigger('click');
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 0, sort: 'time', keyword: 'b', page: 1 }
);
});
test('reset all filters', async () => {
window.$ = jest.fn(() => ({
iCheck() {}
}));
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
const wrapper = mount(List);
jest.runAllTimers();
wrapper.find('input[value=cape]').setChecked();
jest.runAllTimers();
wrapper.find('input[type=text]').setValue('abc');
jest.runAllTimers();
wrapper.find('.dropdown-menu > li:nth-child(1) > a').trigger('click');
jest.runAllTimers();
Vue.prototype.$http.get.mockClear();
wrapper.find('.btn-warning').trigger('click');
expect(Vue.prototype.$http.get).toHaveBeenCalledTimes(1);
});
test('is anonymous', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
const wrapper = mount(List);
expect(wrapper.vm.anonymous).toBeTrue();
});
test('on page changed', () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [], total_pages: 0, current_uid: 0
});
const wrapper = mount(List);
jest.runAllTimers();
wrapper.vm.pageChanged(2);
expect(Vue.prototype.$http.get).toBeCalledWith(
'/skinlib/data',
{ filter: 'skin', uploader: 0, sort: 'time', keyword: '', page: 2 }
);
});
test('on like toggled', async () => {
Vue.prototype.$http.get.mockResolvedValue({
items: [{ tid: 1, liked: false }], total_pages: 1, current_uid: 0
});
const wrapper = mount(List);
await wrapper.vm.$nextTick();
wrapper.vm.onLikeToggled(0, true);
expect(wrapper.vm.items[0].liked).toBeTrue();
});

View File

@ -0,0 +1,131 @@
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import SkinLibItem from '@/components/skinlib/SkinLibItem';
import { flushPromises } from '../../utils';
import { swal } from '@/js/notify';
import toastr from 'toastr';
jest.mock('@/js/notify');
jest.mock('toastr');
test('urls', () => {
const wrapper = mount(SkinLibItem, {
propsData: { tid: 1 }
});
expect(wrapper.find('a').attributes()).toHaveProperty(
'href',
'/skinlib/show/1'
);
expect(wrapper.find('img').attributes()).toHaveProperty(
'src',
'/preview/1.png'
);
});
test('render basic information', () => {
const wrapper = mount(SkinLibItem, {
propsData: {
tid: 1,
name: 'test',
type: 'steve',
}
});
expect(wrapper.text()).toContain('test');
expect(wrapper.text()).toContain('skinlib.filter.steve');
});
test('anonymous user', () => {
const wrapper = mount(SkinLibItem, {
propsData: { anonymous: true }
});
const button = wrapper.find('.more');
expect(button.attributes().title).toBe('skinlib.anonymous');
button.trigger('click');
expect(Vue.prototype.$http.post).not.toBeCalled();
});
test('private texture', () => {
const wrapper = mount(SkinLibItem, {
propsData: { isPublic: false }
});
expect(wrapper.text()).toContain('skinlib.private');
wrapper.setProps({ isPublic: true });
expect(wrapper.text()).not.toContain('skinlib.private');
});
test('liked state', () => {
const wrapper = mount(SkinLibItem, {
propsData: { liked: true, anonymous: false }
});
const button = wrapper.find('.like');
expect(button.attributes()).toHaveProperty('title', 'skinlib.removeFromCloset');
expect(button.classes()).toContain('liked');
wrapper.setProps({ liked: false });
expect(button.attributes()).toHaveProperty('title', 'skinlib.addToCloset');
expect(button.classes()).not.toContain('liked');
});
test('remove from closet', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValue({ errno: 0 });
swal.mockResolvedValueOnce({ dismiss: 1 })
.mockResolvedValue({});
jest.spyOn(toastr, 'warning');
const wrapper = mount(SkinLibItem, {
propsData: { tid: 1, liked: true, anonymous: false }
});
const button = wrapper.find('.like');
button.trigger('click');
expect(Vue.prototype.$http.post).not.toBeCalled();
button.trigger('click');
await flushPromises();
expect(Vue.prototype.$http.post).toBeCalledWith(
'/user/closet/remove',
{ tid: 1 }
);
expect(toastr.warning).toBeCalledWith('1');
button.trigger('click');
await flushPromises();
expect(wrapper.emitted('like-toggled')[0]).toEqual([false]);
});
test('add to closet', async () => {
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValue({ errno: 0 });
swal.mockImplementationOnce(() => ({ dismiss: 1 }))
.mockImplementation(({ inputValidator }) => {
if (inputValidator) {
inputValidator();
inputValidator('name');
}
return { value: 'name' };
});
jest.spyOn(toastr, 'warning');
const wrapper = mount(SkinLibItem, {
propsData: { tid: 1, liked: false, anonymous: false }
});
const button = wrapper.find('.like');
button.trigger('click');
expect(Vue.prototype.$http.post).not.toBeCalled();
button.trigger('click');
await flushPromises();
expect(Vue.prototype.$http.post).toBeCalledWith(
'/user/closet/add',
{ tid: 1, name: 'name' }
);
expect(toastr.warning).toBeCalledWith('1');
button.trigger('click');
await flushPromises();
expect(wrapper.emitted('like-toggled')[0]).toEqual([true]);
});

View File

@ -35,12 +35,16 @@ auth:
reset-button: Reset
skinlib:
reset: Reset Filter
nowShowing: Now showing
addToCloset: Add to closet
removeFromCloset: Remove from closet
setItemName: Set a name for this texture
emptyItemName: Empty texture name.
setNewTextureName: 'Please enter the new texture name:'
emptyNewTextureName: Empty new texture name.
anyModels: Any Models
seeMyUpload: Check My Upload
filter:
skin: (Any Model)
steve: (Steve)
@ -49,6 +53,7 @@ skinlib:
uploader: 'User (UID = :uid) Uploaded'
allUsers: All Users
sort:
title: Sort
time: Newestly Uploaded
likes: Most Likes
badSkinSize: The size of selected skin file is not valid
@ -276,6 +281,8 @@ general:
rotation: Rotation
pause: Pause
reset: Reset
skinlib: Skin Library
loading: Loading
user:
email: Email
nickname: Nick Name

View File

@ -35,12 +35,16 @@ auth:
reset-button: 重置
skinlib:
reset: 清除筛选
nowShowing: 当前正显示
addToCloset: 添加至衣柜
removeFromCloset: 从衣柜中移除
setItemName: 给你的皮肤起个名字吧~
emptyItemName: 你还没有填写要收藏的材质名称啊
anonymous: 请先登录
private: 私密
anyModels: 任意模型
seeMyUpload: 查看我上传的
filter:
skin: (任意模型)
steve: Steve
@ -49,6 +53,7 @@ skinlib:
uploader: '用户UID = :uid上传'
allUsers: 所有用户
sort:
title: 排序
time: 最新上传
likes: 最多收藏
badSkinSize: 所选皮肤文件的尺寸不对哦
@ -270,6 +275,8 @@ general:
rotation: 旋转
pause: 暂停
reset: 重置
skinlib: 皮肤库
loading: 正在加载
user:
email: 邮箱
nickname: 昵称

View File

@ -3,41 +3,5 @@
@section('title', trans('general.skinlib'))
@section('content')
<!-- Full Width Column -->
<div class="content-wrapper">
<div class="container">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>@lang('general.skinlib')
<small id="search-indicator"></small>
</h1>
<ol class="breadcrumb">
<li><i class="fas fa-tags"></i> @lang('skinlib.filter.now-showing')</li>
<li id="filter-indicator"></li>
<li id="uploader-indicator"></li>
<li class="active" id="sort-indicator"></li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<div class="box box-default">
<!-- Container of Skin Library -->
<div id="skinlib-container" class="box-body"></div>
<div class="box-footer">
<!-- Pagination -->
<div class="pull-right" id="skinlib-paginator"></div>
<select class="pull-right pagination"></select>
<p class="pull-right pagination"></p>
</div>
<div class="overlay">
<i class="fas fa-sync-alt fa-spin"></i>
<span>@lang('general.loading')</span>
</div>
</div><!-- /.box -->
</section><!-- /.content -->
</div><!-- /.container -->
</div><!-- /.content-wrapper -->
<div class="content-wrapper"></div>
@endsection

View File

@ -37,60 +37,12 @@
<li>
<a href="{{ url('user/closet') }}">@lang('general.my-closet')</a>
</li>
@unless (isset($with_out_filter))
<!-- Filters -->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-filter" aria-hidden="true"></i> @lang('skinlib.general.filter') <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><a class="filter" data-filter="skin" href="#">@lang('general.skin') <small>@lang('skinlib.filter.any-model')</small></a></li>
<li><a class="filter" data-filter="steve" href="#">@lang('general.skin') <small>@lang('skinlib.filter.steve-model')</small></a></li>
<li><a class="filter" data-filter="alex" href="#">@lang('general.skin') <small>@lang('skinlib.filter.alex-model')</small></a></li>
<li class="divider"></li>
<li><a class="filter" data-filter="cape" href="#">@lang('general.cape')</a></li>
@if (!is_null($user))
<li class="divider"></li>
<li><a class="filter" data-filter="uploader" data-uid="{{ $user->uid }}" href="#">@lang('skinlib.general.my-upload')</a></li>
@endif
<li class="divider"></li>
<li><a href="{{ url('skinlib') }}">@lang('skinlib.filter.clean-filter')</a></li>
</ul>
</li>
<!-- Sort -->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-sort-amount-down" aria-hidden="true"></i> @lang('skinlib.general.sort') <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><a class="sort" data-sort="likes" href="#">@lang('skinlib.sort.most-likes')</a></li>
<li class="divider"></li>
<li><a class="sort" data-sort="time" href="#">@lang('skinlib.sort.newest-uploaded')</a></li>
</ul>
</li>
@endunless
</ul>
@unless (isset($with_out_filter))
<form class="navbar-form navbar-left" id="search-form" role="search">
<div class="form-group">
<input type="text" class="form-control" id="navbar-search-input" name="q" placeholder="@lang('skinlib.general.search-textures')" value="{{ $q or '' }}" />
</div>
</form>
@endunless
</div><!-- /.navbar-collapse -->
<!-- Navbar Right Menu -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li><a href="{{ url('skinlib/upload') }}"><i class="fas fa--fileupload" aria-hidden="true"></i> <span class="description-text">@lang('skinlib.general.upload-new-skin')</span></a></li>
<li><a href="{{ url('skinlib/upload') }}"><i class="fas fa-upload" aria-hidden="true"></i> <span class="description-text">@lang('skinlib.general.upload-new-skin')</span></a></li>
@include('common.language')

View File

@ -54,7 +54,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data')
->assertJson([
'items' => [],
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 0
]);
@ -70,7 +70,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data')
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -81,7 +81,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?filter=steve')
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -89,7 +89,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?filter=what')
->assertJson([
'items' => [],
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 0
]);
@ -100,7 +100,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?filter=cape')
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -115,7 +115,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?uploader='.$uid)
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -123,7 +123,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?sort=tid')
->assertJson([
'items' => $this->serializeTextures($skins->sortByDesc('tid')->values()),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -139,7 +139,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?keyword='.$keyword)
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -155,7 +155,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?sort=likes&keyword='.$keyword)
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 1
]);
@ -173,13 +173,13 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data')
->assertJson([
'items' => $expected,
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 2
]);
$this->getJson('/skinlib/data?page=-5')
->assertJson([
'items' => $expected,
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 2
]);
$expected = $skins
@ -191,19 +191,19 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?page=2')
->assertJson([
'items' => $expected,
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 2
]);
$this->getJson('/skinlib/data?page=8')
->assertJson([
'items' => [],
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 2
]);
$this->getJson('/skinlib/data?items_per_page=-6&page=2')
->assertJson([
'items' => $expected,
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 2
]);
$expected = $skins
@ -214,7 +214,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data?page=3&items_per_page=8')
->assertJson([
'items' => $this->serializeTextures($expected),
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 4
]);
@ -234,7 +234,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data')
->assertJson([
'items' => $expected,
'anonymous' => true,
'current_uid' => 0,
'total_pages' => 2
]);
@ -246,7 +246,7 @@ class SkinlibControllerTest extends TestCase
->getJson('/skinlib/data')
->assertJson([
'items' => $expected,
'anonymous' => false,
'current_uid' => $otherUser->uid,
'total_pages' => 2
]);
@ -268,7 +268,7 @@ class SkinlibControllerTest extends TestCase
$this->getJson('/skinlib/data')
->assertJson([
'items' => $expected,
'anonymous' => false,
'current_uid' => $otherUser->uid,
'total_pages' => 2
]);
@ -292,7 +292,7 @@ class SkinlibControllerTest extends TestCase
->getJson('/skinlib/data')
->assertJson([
'items' => $expected,
'anonymous' => false,
'current_uid' => $uploader->uid,
'total_pages' => 2
]);
@ -302,7 +302,7 @@ class SkinlibControllerTest extends TestCase
->getJson('/skinlib/data')
->assertJson([
'items' => $expected,
'anonymous' => false,
'current_uid' => $admin->uid,
'total_pages' => 2
]);
}