Add players page
This commit is contained in:
parent
519782e0f3
commit
ca1f5fdb69
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title" style="width: 100%;">{{ $t('general.texturePreview') }}
|
||||
<h3 class="box-title" style="width: 100%;">
|
||||
<span v-t="title"></span>
|
||||
<span data-toggle="tooltip" class="badge bg-light-blue">{{ indicator }}</span>
|
||||
<div class="operations">
|
||||
<i
|
||||
|
|
@ -57,6 +58,9 @@ export default {
|
|||
skin: String,
|
||||
cape: String,
|
||||
closetMode: Boolean,
|
||||
title: {
|
||||
default: 'general.texturePreview'
|
||||
},
|
||||
initPositionZ: {
|
||||
default: 70
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ export default [
|
|||
component: () => import('./user/Closet'),
|
||||
el: '.content'
|
||||
},
|
||||
{
|
||||
path: 'user/player',
|
||||
component: () => import('./user/Players'),
|
||||
el: '.content'
|
||||
},
|
||||
{
|
||||
path: 'user/profile',
|
||||
component: () => import('./user/Profile'),
|
||||
|
|
|
|||
477
resources/assets/src/components/user/Players.vue
Normal file
477
resources/assets/src/components/user/Players.vue
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
<template>
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box box-primary">
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PID</th>
|
||||
<th v-t="'general.player.player-name'"></th>
|
||||
<th>
|
||||
{{ $t('user.player.preference.title') }}
|
||||
<i
|
||||
class="fas fa-question-circle"
|
||||
:title="$t('user.player.preference.description')"
|
||||
data-toggle="tooltip"
|
||||
data-placement="right"
|
||||
></i>
|
||||
</th>
|
||||
<th v-t="'user.player.edit'"></th>
|
||||
<th v-t="'user.player.operation'"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="player in players"
|
||||
:key="player.pid"
|
||||
class="player"
|
||||
:class="{ 'player-selected': player.pid === selected }"
|
||||
@click="preview(player)"
|
||||
>
|
||||
<td class="pid">{{ player.pid }}</td>
|
||||
<td class="player-name">{{ player.player_name }}</td>
|
||||
<td>
|
||||
<select class="form-control" @change="togglePreference(player)">
|
||||
<option value="default" :selected="player.preference === 'default'">Default (Steve)</option>
|
||||
<option value="slim" :selected="player.preference === 'slim'">Slim (Alex)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
class="btn btn-default btn-sm"
|
||||
@click="changeName(player)"
|
||||
v-t="'user.player.edit-pname'"
|
||||
></a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
class="btn btn-warning btn-sm"
|
||||
data-toggle="modal"
|
||||
data-target="#modal-clear-texture"
|
||||
@click="loadICheck"
|
||||
v-t="'user.player.delete-texture'"
|
||||
></a>
|
||||
<a
|
||||
class="btn btn-danger btn-sm"
|
||||
@click="deletePlayer(player)"
|
||||
v-t="'user.player.delete-player'"
|
||||
></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer clearfix">
|
||||
<button class="btn btn-primary pull-left" data-toggle="modal" data-target="#modal-add-player">
|
||||
<i class="fas fa-plus" aria-hidden="true"></i> {{ $t('user.player.add-player') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-once class="box box-default collapsed-box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title" v-t="'general.tip'"></h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fas fa-plus"></i></button>
|
||||
</div><!-- /.box-tools -->
|
||||
</div><!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<p v-t="'user.player.login-notice'"></p>
|
||||
</div><!-- /.box-body -->
|
||||
</div><!-- /.box -->
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<previewer
|
||||
v-if="using3dPreviewer"
|
||||
:skin="skinUrl"
|
||||
:cape="capeUrl"
|
||||
title="user.player.player-info"
|
||||
>
|
||||
<template slot="footer">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
@click="togglePreviewer"
|
||||
v-t="'user.switch2dPreview'"
|
||||
data-test="to2d"
|
||||
></button>
|
||||
</template>
|
||||
</previewer>
|
||||
<div v-else class="box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title" v-t="'user.player.player-info'"></h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div id="preview-2d">
|
||||
<p>
|
||||
{{ $t('user.player.textures.steve') }}
|
||||
<a v-if="preview2d.steve" :href="`${baseUrl}/skinlib/show/${preview2d.steve}`">
|
||||
<img
|
||||
class="skin2d"
|
||||
:src="`${baseUrl}/preview/64/${preview2d.steve}.png`"
|
||||
/>
|
||||
</a>
|
||||
<span v-else class="skin2d" v-t="'user.player.textures.empty'"></span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ $t('user.player.textures.alex') }}
|
||||
<a v-if="preview2d.alex" :href="`${baseUrl}/skinlib/show/${preview2d.alex}`">
|
||||
<img
|
||||
class="skin2d"
|
||||
:src="`${baseUrl}/preview/64/${preview2d.alex}.png`"
|
||||
/>
|
||||
</a>
|
||||
<span v-else class="skin2d" v-t="'user.player.textures.empty'"></span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ $t('user.player.textures.cape') }}
|
||||
<a v-if="preview2d.cape" :href="`${baseUrl}/skinlib/show/${preview2d.cape}`">
|
||||
<img
|
||||
class="skin2d"
|
||||
:src="`${baseUrl}/preview/64/${preview2d.cape}.png`"
|
||||
/>
|
||||
</a>
|
||||
<span v-else class="skin2d" v-t="'user.player.textures.empty'"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- /.box-body -->
|
||||
<div class="box-footer">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
@click="togglePreviewer"
|
||||
v-t="'user.switch3dPreview'"
|
||||
></button>
|
||||
</div>
|
||||
</div><!-- /.box -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="modal-add-player"
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" v-t="'user.player.add-player'"></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="key" v-t="'general.player.player-name'"></td>
|
||||
<td class="value">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="newPlayer"
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="callout callout-info">
|
||||
<ul style="padding: 0 0 0 20px; margin: 0;">
|
||||
<li>{{ playerNameRule }}</li>
|
||||
<li>{{ playerNameLength }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="modal"
|
||||
v-t="'general.close'"
|
||||
></button>
|
||||
<a @click="addPlayer" class="btn btn-primary" v-t="'general.submit'"></a>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="modal-clear-texture"
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" v-t="'user.chooseClearTexture'"></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label class="form-group">
|
||||
<input type="checkbox" v-model="clear.steve"> Default (Steve)
|
||||
</label>
|
||||
<br>
|
||||
<label class="form-group">
|
||||
<input type="checkbox" v-model="clear.alex"> Slim (Alex)
|
||||
</label>
|
||||
<br>
|
||||
<label class="form-group">
|
||||
<input type="checkbox" v-model="clear.cape"> {{ $t('general.cape') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="modal"
|
||||
v-t="'general.close'"
|
||||
></button>
|
||||
<a @click="clearTexture" class="btn btn-primary" v-t="'general.submit'"></a>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SkinAlex from '../../images/textures/alex.png';
|
||||
import { swal } from '../../js/notify';
|
||||
import toastr from 'toastr';
|
||||
|
||||
export default {
|
||||
name: 'Players',
|
||||
components: {
|
||||
Previewer: () => import('../common/Previewer')
|
||||
},
|
||||
props: {
|
||||
baseUrl: {
|
||||
default: blessing.base_url
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
players: [],
|
||||
selected: 0,
|
||||
using3dPreviewer: true,
|
||||
skinUrl: '',
|
||||
capeUrl: '',
|
||||
preview2d: {
|
||||
steve: 0,
|
||||
alex: 0,
|
||||
cape: 0
|
||||
},
|
||||
newPlayer: '',
|
||||
clear: {
|
||||
steve: false,
|
||||
alex: false,
|
||||
cape: false
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
playerNameRule: () => __bs_data__.rule,
|
||||
playerNameLength: () => __bs_data__.length
|
||||
},
|
||||
beforeMount() {
|
||||
this.fetchPlayers();
|
||||
},
|
||||
methods: {
|
||||
async fetchPlayers() {
|
||||
this.players = await this.$http.get('/user/player/list');
|
||||
},
|
||||
togglePreviewer() {
|
||||
this.using3dPreviewer = !this.using3dPreviewer;
|
||||
},
|
||||
async preview(player) {
|
||||
this.selected = player.pid;
|
||||
|
||||
this.preview2d.steve = player.tid_steve;
|
||||
this.preview2d.alex = player.tid_alex;
|
||||
this.preview2d.cape = player.tid_cape;
|
||||
|
||||
if (player.preference === 'default') {
|
||||
if (player.tid_steve) {
|
||||
const steve = await this.$http.get(`/skinlib/info/${player.tid_steve}`);
|
||||
this.skinUrl = `${this.baseUrl}/textures/${steve.hash}`;
|
||||
} else {
|
||||
this.skinUrl = '';
|
||||
}
|
||||
} else {
|
||||
if (player.tid_alex) {
|
||||
const alex = await this.$http.get(`/skinlib/info/${player.tid_alex}`);
|
||||
this.skinUrl = `${this.baseUrl}/textures/${alex.hash}`;
|
||||
} else {
|
||||
this.skinUrl = SkinAlex;
|
||||
}
|
||||
}
|
||||
if (player.tid_cape) {
|
||||
const cape = await this.$http.get(`/skinlib/info/${player.tid_cape}`);
|
||||
this.capeUrl = `${this.baseUrl}/textures/${cape.hash}`;
|
||||
} else {
|
||||
this.capeUrl = '';
|
||||
}
|
||||
},
|
||||
async togglePreference(player) {
|
||||
const preference = player.preference === 'default' ? 'slim' : 'default';
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/player/preference',
|
||||
{ pid: player.pid, preference }
|
||||
);
|
||||
if (errno === 0) {
|
||||
player.preference = preference;
|
||||
toastr.success(msg);
|
||||
} else {
|
||||
toastr.warning(msg);
|
||||
}
|
||||
},
|
||||
async changeName(player) {
|
||||
const { dismiss, value } = await swal({
|
||||
title: this.$t('user.changePlayerName'),
|
||||
inputValue: player.player_name,
|
||||
input: 'text',
|
||||
showCancelButton: true,
|
||||
inputValidator: /* istanbul ignore next */ value =>
|
||||
!value && this.$t('user.emptyPlayerName')
|
||||
});
|
||||
if (dismiss) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/player/rename',
|
||||
{ pid: player.pid, new_player_name: value }
|
||||
);
|
||||
if (errno === 0) {
|
||||
swal({ type: 'success', html: msg });
|
||||
player.player_name = value;
|
||||
} else {
|
||||
swal({ type: 'warning', html: msg });
|
||||
}
|
||||
},
|
||||
loadICheck() {
|
||||
$('input').iCheck({
|
||||
radioClass: 'iradio_square-blue',
|
||||
checkboxClass: 'icheckbox_square-blue'
|
||||
}).on('ifChecked ifUnchecked', function () {
|
||||
$(this)[0].dispatchEvent(new Event('change'));
|
||||
});
|
||||
},
|
||||
async clearTexture() {
|
||||
if (Object.values(this.clear).every(value => !value)) {
|
||||
return toastr.warning(this.$t('user.noClearChoice'));
|
||||
}
|
||||
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/player/texture/clear',
|
||||
{ pid: this.selected, ...this.clear }
|
||||
);
|
||||
if (errno === 0) {
|
||||
$('.modal').modal('hide');
|
||||
swal({ type: 'success', text: msg });
|
||||
const player = this.players.find(({ pid }) => pid === this.selected);
|
||||
Object.keys(this.clear)
|
||||
.filter(type => this.clear[type])
|
||||
.forEach(type => player[`tid_${type}`] = 0);
|
||||
} else {
|
||||
swal({ type: 'warning', text: msg });
|
||||
}
|
||||
},
|
||||
async deletePlayer(player) {
|
||||
const { dismiss } = await swal({
|
||||
title: this.$t('user.deletePlayer'),
|
||||
text: this.$t('user.deletePlayerNotice'),
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
cancelButtonColor: '#3085d6',
|
||||
confirmButtonColor: '#d33'
|
||||
});
|
||||
if (dismiss) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/player/delete',
|
||||
{ pid: player.pid }
|
||||
);
|
||||
if (errno === 0) {
|
||||
swal({ type: 'success', html: msg });
|
||||
this.players = this.players.filter(({ pid }) => pid !== player.pid);
|
||||
} else {
|
||||
swal({ type: 'warning', html: msg });
|
||||
}
|
||||
},
|
||||
async addPlayer() {
|
||||
$('.modal').modal('hide');
|
||||
const { errno, msg } = await this.$http.post(
|
||||
'/user/player/add',
|
||||
{ player_name: this.newPlayer }
|
||||
);
|
||||
if (errno === 0) {
|
||||
await swal({ type: 'success', html: msg });
|
||||
this.fetchPlayers();
|
||||
} else {
|
||||
swal({ type: 'warning', html: msg });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.player {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
|
||||
#preference {
|
||||
height: 31px;
|
||||
}
|
||||
|
||||
.pid, .player-name {
|
||||
padding-top: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.player:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.player-selected {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.modal-body > label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.skin2d {
|
||||
float: right;
|
||||
max-height: 64px;
|
||||
width: 64px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#preview-2d > p {
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
BIN
resources/assets/src/images/textures/alex.png
Normal file
BIN
resources/assets/src/images/textures/alex.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable no-var */
|
||||
import 'core-js/fn/array/includes';
|
||||
import 'core-js/fn/array/find';
|
||||
import 'core-js/fn/object/values';
|
||||
import 'es6-promise/auto';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
|
|
|
|||
|
|
@ -88,3 +88,8 @@ test('reset', () => {
|
|||
wrapper.find('.fa-stop').trigger('click');
|
||||
expect(mockedSkinview3d.SkinViewer.prototype.dispose).toBeCalled();
|
||||
});
|
||||
|
||||
test('custom title', () => {
|
||||
const wrapper = mount(Previewer, { propsData: { title: 'custom-title' } });
|
||||
expect(wrapper.text()).toContain('custom-title');
|
||||
});
|
||||
|
|
|
|||
218
resources/assets/tests/components/user/Players.test.js
Normal file
218
resources/assets/tests/components/user/Players.test.js
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import Vue from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { flushPromises } from '../../utils';
|
||||
import Players from '@/components/user/Players';
|
||||
import { swal } from '@/js/notify';
|
||||
|
||||
jest.mock('toastr');
|
||||
jest.mock('@/js/notify');
|
||||
|
||||
window.__bs_data__ = {
|
||||
rule: 'rule',
|
||||
length: 'length'
|
||||
};
|
||||
|
||||
test('display player name constraints', () => {
|
||||
const wrapper = mount(Players);
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('rule');
|
||||
expect(text).toContain('length');
|
||||
});
|
||||
|
||||
test('fetch players data before mount', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValue([]);
|
||||
mount(Players);
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith('/user/player/list');
|
||||
});
|
||||
|
||||
test('click to preview player', async () => {
|
||||
Vue.prototype.$http.get
|
||||
.mockResolvedValueOnce([
|
||||
{ pid: 1, preference: 'default', tid_steve: 1, tid_alex: 2, tid_cape: 3 },
|
||||
{ pid: 2, preference: 'default', tid_steve: 0, tid_alex: 2, tid_cape: 0 },
|
||||
{ pid: 3, preference: 'slim', tid_steve: 1, tid_alex: 2, tid_cape: 0 },
|
||||
{ pid: 4, preference: 'slim', tid_steve: 0, tid_alex: 0, tid_cape: 0 },
|
||||
])
|
||||
.mockResolvedValueOnce({ hash: 'a' })
|
||||
.mockResolvedValueOnce({ hash: 'b' })
|
||||
.mockResolvedValueOnce({ hash: 'c' });
|
||||
const wrapper = mount(Players);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.find('tbody > tr:nth-child(1)').trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith('/skinlib/info/1');
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith('/skinlib/info/3');
|
||||
expect(wrapper.findAll('.player').at(0).classes()).toContain('player-selected');
|
||||
|
||||
wrapper.find('tbody > tr:nth-child(2)').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
wrapper.find('tbody > tr:nth-child(3)').trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.get).toBeCalledWith('/skinlib/info/2');
|
||||
|
||||
wrapper.find('tbody > tr:nth-child(4)').trigger('click');
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
test('toggle preference', async () => {
|
||||
Vue.prototype.$http.get
|
||||
.mockResolvedValueOnce([
|
||||
{ pid: 1, preference: 'default' },
|
||||
{ pid: 2, preference: 'slim' }
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1 })
|
||||
.mockResolvedValue({ errno: 0 });
|
||||
const wrapper = mount(Players);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.findAll('select').at(0).trigger('change');
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/player/preference',
|
||||
{ pid: 1, preference: 'slim' }
|
||||
);
|
||||
|
||||
wrapper.findAll('select').at(1).trigger('change');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/player/preference',
|
||||
{ pid: 2, preference: 'default' }
|
||||
);
|
||||
});
|
||||
|
||||
test('change player name', async () => {
|
||||
Vue.prototype.$http.get
|
||||
.mockResolvedValueOnce([
|
||||
{ pid: 1, player_name: 'old' },
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1 })
|
||||
.mockResolvedValue({ errno: 0 });
|
||||
swal.mockResolvedValueOnce({ dismiss: 1 })
|
||||
.mockResolvedValue({ value: 'new-name' });
|
||||
const wrapper = mount(Players);
|
||||
await wrapper.vm.$nextTick();
|
||||
const button = wrapper.find('.btn-default');
|
||||
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/player/rename',
|
||||
{ pid: 1, new_player_name: 'new-name' }
|
||||
);
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(wrapper.text()).toContain('new-name');
|
||||
});
|
||||
|
||||
test('load iCheck', async () => {
|
||||
Vue.prototype.$http.get
|
||||
.mockResolvedValueOnce([
|
||||
{ pid: 1 },
|
||||
]);
|
||||
window.$ = jest.fn(() => ({
|
||||
iCheck: () => ({
|
||||
on(evt, cb) {
|
||||
cb();
|
||||
},
|
||||
}),
|
||||
0: {
|
||||
dispatchEvent: () => {}
|
||||
}
|
||||
}));
|
||||
const wrapper = mount(Players);
|
||||
await wrapper.vm.$nextTick();
|
||||
wrapper.find('.btn-warning').trigger('click');
|
||||
expect(window.$).toBeCalled();
|
||||
});
|
||||
|
||||
test('delete player', async () => {
|
||||
Vue.prototype.$http.get
|
||||
.mockResolvedValueOnce([
|
||||
{ pid: 1, player_name: 'to-be-deleted' },
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1 })
|
||||
.mockResolvedValue({ errno: 0 });
|
||||
swal.mockResolvedValueOnce({ dismiss: 1 })
|
||||
.mockResolvedValue({});
|
||||
const wrapper = mount(Players);
|
||||
await wrapper.vm.$nextTick();
|
||||
const button = wrapper.find('.btn-danger');
|
||||
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
button.trigger('click');
|
||||
expect(wrapper.text()).toContain('to-be-deleted');
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises(); // Finish HTTP request
|
||||
await wrapper.vm.$nextTick(); // Update DOM
|
||||
expect(wrapper.text()).not.toContain('to-be-deleted');
|
||||
});
|
||||
|
||||
test('toggle preview mode', () => {
|
||||
Vue.prototype.$http.get.mockResolvedValueOnce([]);
|
||||
const wrapper = mount(Players);
|
||||
wrapper.find('[data-test="to2d"]').trigger('click');
|
||||
expect(wrapper.text()).toContain('user.player.textures.empty');
|
||||
});
|
||||
|
||||
test('add player', async () => {
|
||||
window.$ = jest.fn(() => ({ modal() {} }));
|
||||
Vue.prototype.$http.get.mockResolvedValueOnce([]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1 })
|
||||
.mockResolvedValue({ errno: 0 });
|
||||
const wrapper = mount(Players);
|
||||
const button = wrapper.findAll('.modal-footer').at(0).find('a');
|
||||
wrapper.find('input[type="text"]').setValue('the-new');
|
||||
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/player/add',
|
||||
{ player_name: 'the-new' }
|
||||
);
|
||||
await flushPromises();
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.text()).not.toContain('the-new');
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(Vue.prototype.$http.get).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('clear texture', async () => {
|
||||
window.$ = jest.fn(() => ({ modal() {} }));
|
||||
Vue.prototype.$http.get.mockResolvedValueOnce([
|
||||
{ pid: 1, tid_steve: 1, tid_alex: 0, tid_cape: 0 }
|
||||
]);
|
||||
Vue.prototype.$http.post
|
||||
.mockResolvedValueOnce({ errno: 1 })
|
||||
.mockResolvedValue({ errno: 0, msg: 'ok' });
|
||||
const wrapper = mount(Players);
|
||||
await wrapper.vm.$nextTick();
|
||||
const button = wrapper.findAll('.modal-footer').at(1).find('a');
|
||||
wrapper.find('.player').trigger('click');
|
||||
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).not.toBeCalled();
|
||||
|
||||
wrapper.findAll('input[type="checkbox"]').at(0).setChecked();
|
||||
button.trigger('click');
|
||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
||||
'/user/player/texture/clear',
|
||||
{ pid: 1, steve: true, alex: false, cape: false }
|
||||
);
|
||||
|
||||
button.trigger('click');
|
||||
await flushPromises();
|
||||
expect(swal).toBeCalledWith({ type: 'success', text: 'ok' });
|
||||
});
|
||||
|
|
@ -138,6 +138,31 @@ user:
|
|||
time-unit-min: min
|
||||
last-sign: Last signed at :time
|
||||
sign-remain-time: Available after :time :unit
|
||||
player:
|
||||
player-name: Player Name
|
||||
edit: Edit
|
||||
operation: Delete
|
||||
edit-pname: Player name
|
||||
delete-texture: Textures
|
||||
delete-player: Player
|
||||
add-player: Add new player
|
||||
login-notice: Now you can log in with player names you owned instead email address.
|
||||
player-info: Information <small>(click player name to show preview)</small>
|
||||
textures:
|
||||
steve: Steve Model:
|
||||
alex: Alex Model:
|
||||
cape: Cape:
|
||||
empty: Nothing
|
||||
pname-rule: Could only contain letters, numbers and dashes.
|
||||
pname-rule-chinese: Could only contain chinese characters, letters, numbers and dashes.
|
||||
player-name-rule:
|
||||
official: Player name may only contains letters, numbers and underscores.
|
||||
cjk: Player name may contains letters, numbers, underscores and CJK Unified Ideographs.
|
||||
custom: Custom player name rules are applied on this site. Please contact admins for further information.
|
||||
player-name-length: The player name should be at least :min characters and not greater than :max characters.
|
||||
preference:
|
||||
title: Preference
|
||||
description: Default stands for the Steve model with 4-pixel width arms, and Slim for the Alex model with 3-pixel width arms.
|
||||
|
||||
admin:
|
||||
operationsTitle: Operations
|
||||
|
|
@ -225,6 +250,7 @@ general:
|
|||
submit: Submit
|
||||
close: Close
|
||||
more: More
|
||||
tip: Tip
|
||||
pagination: 'Page :page, total :total'
|
||||
searchResult: '(Search result of keyword ":keyword")'
|
||||
noResult: No result.
|
||||
|
|
|
|||
|
|
@ -139,6 +139,28 @@ user:
|
|||
time-unit-min: 分钟
|
||||
last-sign: 上次签到于 :time
|
||||
sign-remain-time: :time :unit后可签到
|
||||
player:
|
||||
edit: 编辑
|
||||
operation: 操作
|
||||
edit-pname: 修改角色名
|
||||
delete-texture: 删除材质
|
||||
delete-player: 删除角色
|
||||
add-player: 添加新角色
|
||||
login-notice: 你现在可以使用你所拥有的角色名来登录皮肤站啦~
|
||||
player-info: 角色信息 <small>(点击角色名以查看预览)</small>
|
||||
textures:
|
||||
steve: Steve 模型的皮肤:
|
||||
alex: Alex 模型的皮肤:
|
||||
cape: 披风:
|
||||
empty: 未上传
|
||||
player-name-rule:
|
||||
official: 角色名只能包含拉丁字母、数字以及下划线。
|
||||
cjk: 角色名可使用拉丁字母、数字、下划线以及汉字(中日韩统一表意文字)。
|
||||
custom: 本站使用了自定义的角色名规则,详情请咨询站点管理员。
|
||||
player-name-length: 角色名最少要求 :min 个字符,最多不超过 :max 个字符。
|
||||
preference:
|
||||
title: 优先模型
|
||||
description: Default 即为默认的 Steve 模型(手臂 4 个像素宽),Slim 即为手臂宽 3 个像素的 Alex 模型。
|
||||
|
||||
admin:
|
||||
operationsTitle: 更多操作
|
||||
|
|
@ -222,6 +244,7 @@ general:
|
|||
submit: 提交
|
||||
close: 关闭
|
||||
more: 更多
|
||||
tip: 提示
|
||||
pagination: '第 :page 页,共 :total 页'
|
||||
searchResult: '(关键词 “:keyword” 的搜索结果)'
|
||||
noResult: 无结果
|
||||
|
|
|
|||
|
|
@ -14,140 +14,14 @@
|
|||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="box box-primary">
|
||||
<div class="box-body table-responsive no-padding">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PID</th>
|
||||
<th>@lang('user.player.player-name')</th>
|
||||
<th>
|
||||
@lang('user.player.preference.title')
|
||||
<i class="fas fa-question-circle" title="@lang('user.player.preference.description')" data-toggle="tooltip" data-placement="right"></i>
|
||||
</th>
|
||||
<th>@lang('user.player.edit')</th>
|
||||
<th>@lang('user.player.operation')</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach ($players as $player)
|
||||
<tr class="player" id="{{ $player['pid'] }}">
|
||||
<td class="pid">{{ $player['pid'] }}</td>
|
||||
<td class="player-name">{{ $player['player_name'] }}</td>
|
||||
<td>
|
||||
<select class="form-control" id="preference" pid="{{ $player['pid'] }}">
|
||||
<option {{ ($player['preference'] == "default") ? 'selected="selected"' : '' }} value="default">Default (Steve)</option>
|
||||
<option {{ ($player['preference'] == "slim") ? 'selected="selected"' : '' }} value="slim">Slim (Alex)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-default btn-sm" onclick="changePlayerName('{{ $player['pid'] }}', '{{ $player['player_name'] }}')">@lang('user.player.edit-pname')</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-warning btn-sm" onclick="clearTexture('{{ $player['pid'] }}');">@lang('user.player.delete-texture')</a>
|
||||
<a class="btn btn-danger btn-sm" onclick="deletePlayer('{{ $player['pid'] }}');">@lang('user.player.delete-player')</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer clearfix">
|
||||
<button class="btn btn-primary pull-left" data-toggle="modal" data-target="#modal-add-player">
|
||||
<i class="fas fa-plus" aria-hidden="true"></i> @lang('user.player.add-player')
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box box-default collapsed-box">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">@lang('general.notice')</h3>
|
||||
<div class="box-tools pull-right">
|
||||
<button class="btn btn-box-tool" data-widget="collapse"><i class="fas fa-plus"></i></button>
|
||||
</div><!-- /.box-tools -->
|
||||
</div><!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
<p>@lang('user.player.login-notice')</p>
|
||||
</div><!-- /.box-body -->
|
||||
</div><!-- /.box -->
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="box">
|
||||
<!-- 3D skin preview -->
|
||||
@include('common.texture-preview', ['title' => trans('user.player.player-info') ])
|
||||
<!-- 2D skin preview -->
|
||||
<div class="box-body">
|
||||
<div id="preview-2d-container" style="display: none;">
|
||||
<p>@lang('user.player.textures.steve')<a href=""><img id="steve" class="skin2d" /></a>
|
||||
<span class="skin2d">@lang('user.player.textures.empty')</span>
|
||||
</p>
|
||||
|
||||
<p>@lang('user.player.textures.alex')<a href=""><img id="alex" class="skin2d" /></a>
|
||||
<span class="skin2d">@lang('user.player.textures.empty')</span>
|
||||
</p>
|
||||
|
||||
<p>@lang('user.player.textures.cape')<a href=""><img id="cape" class="skin2d" /></a>
|
||||
<span class="skin2d">@lang('user.player.textures.empty')</span>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- /.box-body -->
|
||||
<div class="box-footer">
|
||||
<button id="preview-switch" class="btn btn-default">@lang('general.switch-2d-preview')</button>
|
||||
</div>
|
||||
</div><!-- /.box -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section><!-- /.content -->
|
||||
<section class="content"></section><!-- /.content -->
|
||||
</div><!-- /.content-wrapper -->
|
||||
|
||||
<div id="modal-add-player" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">@lang('user.player.add-player')</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="key">@lang('user.player.player-name')</td>
|
||||
<td class="value">
|
||||
<input type="text" class="form-control" id="player_name" value="">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="callout callout-info">
|
||||
<ul style="padding: 0 0 0 20px; margin: 0;">
|
||||
<li>@lang('user.player.player-name-rule.'.option('player_name_rule'))</li>
|
||||
<li>@lang('user.player.player-name-length', ['min' => option('player_name_length_min'), 'max' => option('player_name_length_max')])</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">@lang('general.close')</button>
|
||||
<a onclick="addNewPlayer();" class="btn btn-primary">@lang('general.submit')</a>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$.msp.config.skinUrl = defaultSteveSkin;
|
||||
initSkinViewer();
|
||||
registerAnimationController();
|
||||
registerWindowResizeHandler();
|
||||
});
|
||||
var __bs_data__ = {
|
||||
rule: "@lang('user.player.player-name-rule.'.option('player_name_rule'))",
|
||||
length: "@lang('user.player.player-name-length', ['min' => option('player_name_length_min'), 'max' => option('player_name_length_max')])"
|
||||
}
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user