Add players page

This commit is contained in:
Pig Fang 2018-08-11 11:59:11 +08:00
parent 519782e0f3
commit ca1f5fdb69
10 changed files with 766 additions and 133 deletions

View File

@ -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
}

View File

@ -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'),

View 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> &nbsp;{{ $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">&times;</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">&times;</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -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';

View File

@ -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');
});

View 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' });
});

View File

@ -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.

View File

@ -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: 无结果

View File

@ -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> &nbsp;@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">&times;</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