Remove settings of preference (#8)

Resolve #6
This commit is contained in:
Pig Fang 2019-03-02 21:13:17 +08:00 committed by GitHub
parent b5f35f6379
commit 1ff8e631fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 551 additions and 610 deletions

View File

@ -0,0 +1,64 @@
<?php
namespace App\Console\Commands;
use App\Models\Player;
use Illuminate\Console\Command;
class MigratePlayersTable extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'bs:migrate-v4:players-table';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Migrate the players table for v4';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$players = Player::where('tid_skin', -1)->get();
$count = $players->count();
if ($count == 0) {
$this->info('No need to update.');
return;
}
$this->info('We are going to update your `players` table. Please wait...');
$bar = $this->output->createProgressBar($count);
$players->each(function ($player) use ($bar) {
$player->tid_skin = $player->preference == 'default'
? $player->tid_steve
: $player->tid_alex;
$player->save();
$bar->advance();
});
$bar->finish();
$this->info("\nCongratulations! We've updated $count rows.");
}
}

View File

@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel
Commands\Inspire::class,
Commands\KeyRandomCommand::class,
Commands\SaltRandomCommand::class,
Commands\MigratePlayersTable::class,
];
/**

View File

@ -279,7 +279,7 @@ class AdminController extends Controller
$isSpecifiedUser = $request->has('uid');
if ($isSpecifiedUser) {
$players = Player::select(['pid', 'uid', 'player_name', 'preference', 'tid_steve', 'tid_alex', 'tid_cape', 'last_modified'])
$players = Player::select(['pid', 'uid', 'player_name', 'tid_skin', 'tid_cape', 'last_modified'])
->where('uid', intval($request->input('uid')))
->get();
} else {
@ -289,11 +289,10 @@ class AdminController extends Controller
$page = $request->input('page', 1);
$perPage = $request->input('perPage', 10);
$players = Player::select(['pid', 'uid', 'player_name', 'preference', 'tid_steve', 'tid_alex', 'tid_cape', 'last_modified'])
$players = Player::select(['pid', 'uid', 'player_name', 'tid_skin', 'tid_cape', 'last_modified'])
->where('pid', 'like', '%' . $search . '%')
->orWhere('uid', 'like', '%' . $search . '%')
->orWhere('player_name', 'like', '%' . $search . '%')
->orWhere('preference', 'like', '%' . $search . '%')
->orderBy($sortField, $sortType)
->offset(($page - 1) * $perPage)
->limit($perPage)
@ -425,25 +424,16 @@ class AdminController extends Controller
}
}
if ($action == "preference") {
if ($action == "texture") {
$this->validate($request, [
'preference' => 'required|preference'
]);
$player->setPreference($request->input('preference'));
return json(trans('admin.players.preference.success', ['player' => $player->player_name, 'preference' => $request->input('preference')]), 0);
} elseif ($action == "texture") {
$this->validate($request, [
'model' => 'required|model',
'tid' => 'required|integer'
'type' => 'required',
'tid' => 'required|integer'
]);
if (! Texture::find($request->tid) && $request->tid != 0)
return json(trans('admin.players.textures.non-existent', ['tid' => $request->tid]), 1);
$player->setTexture(['tid_'.$request->model => $request->tid]);
$player->setTexture(['tid_'.$request->type => $request->tid]);
return json(trans('admin.players.textures.success', ['player' => $player->player_name]), 0);

View File

@ -138,7 +138,7 @@ class AuthController extends Controller
$player = new Player;
$player->uid = $user->uid;
$player->player_name = $request->get('player_name');
$player->preference = 'default';
$player->tid_skin = 0;
$player->last_modified = get_datetime_string();
$player->save();

View File

@ -42,7 +42,7 @@ class PlayerController extends Controller
});
$this->middleware([CheckPlayerExist::class, CheckPlayerOwner::class], [
'only' => ['delete', 'rename', 'setTexture', 'clearTexture', 'setPreference']
'only' => ['delete', 'rename', 'setTexture', 'clearTexture']
]);
}
@ -58,7 +58,7 @@ class PlayerController extends Controller
{
return Auth::user()
->players()
->select('pid', 'player_name', 'preference', 'tid_steve', 'tid_alex')
->select('pid', 'player_name', 'tid_skin', 'tid_cape')
->get();
}
@ -86,7 +86,7 @@ class PlayerController extends Controller
$player->uid = $user->uid;
$player->player_name = $request->input('player_name');
$player->preference = "default";
$player->tid_skin = 0;
$player->last_modified = get_datetime_string();
$player->save();
@ -153,24 +153,17 @@ class PlayerController extends Controller
return json(trans('skinlib.un-existent'), 6);
}
$fieldName = "tid_{$texture->type}";
$fieldName = $texture->type == 'cape' ? 'tid_cape' : 'tid_skin';
$this->player->setTexture([$fieldName => $value]);
}
// When user applies an alex skin to a newly added player,
// which textures and model preference are all default value,
// we will automatically set the player's model preference to "slim".
if ($this->player->preference == 'default' && $this->player->tid_steve == 0 && $this->player->tid_alex != 0) {
$this->player->setPreference('slim');
}
return json(trans('user.player.set.success', ['name' => $this->player->player_name]), 0);
}
public function clearTexture(Request $request)
{
$types = array_filter(['steve', 'alex', 'cape'], function ($type) use ($request) {
$types = array_filter(['skin', 'cape'], function ($type) use ($request) {
return $request->input($type);
});
@ -179,15 +172,4 @@ class PlayerController extends Controller
return json(trans('user.player.clear.success', ['name' => $this->player->player_name]), 0);
}
public function setPreference(Request $request)
{
$this->validate($request, [
'preference' => 'required|preference'
]);
$this->player->setPreference($request->preference);
return json(trans('user.player.preference.success', ['name' => $this->player->player_name, 'preference' => $request->preference]), 0);
}
}

View File

@ -271,7 +271,7 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.lack-score'), 1);
}
$type = $t->type;
$type = $t->type == 'cape' ? 'cape' : 'skin';
Player::where("tid_$type", $t->tid)
->where('uid', '<>', session('uid'))
->get()

View File

@ -70,19 +70,9 @@ class TextureController extends Controller
return $this->texture($hash);
}
public function skin($player_name, $model = "")
public function skin($player_name)
{
$player = $this->getPlayerInstance($player_name);
$model_preference = ($player->getPreference() == "default") ? "steve" : "alex";
$model = ($model == "") ? $model_preference : $model;
return $this->getBinaryTextureFromPlayer($player_name, $model);
}
public function skinWithModel($model, $player_name)
{
return $this->skin($player_name, $model);
return $this->getBinaryTextureFromPlayer($player_name, 'skin');
}
public function cape($player_name)
@ -95,7 +85,7 @@ class TextureController extends Controller
*
* @param string $player_name
* @param string $type "steve" or "alex" or "cape".
* @return void|Response
* @return Response
*/
protected function getBinaryTextureFromPlayer($player_name, $type)
{

View File

@ -18,17 +18,14 @@ class Player extends Model
const CSL_API = 0;
const USM_API = 1;
/**
* Set of models.
*/
protected static $models = ['steve', 'alex', 'cape'];
protected static $types = ['skin', 'cape'];
/**
* Properties for Eloquent Model.
*/
public $primaryKey = 'pid';
public $timestamps = false;
protected $fillable = ['uid', 'player_name', 'preference', 'last_modified'];
protected $fillable = ['uid', 'player_name', 'last_modified'];
/**
* The attributes that should be cast to native types.
@ -38,8 +35,7 @@ class Player extends Model
protected $casts = [
'pid' => 'integer',
'uid' => 'integer',
'tid_steve' => 'integer',
'tid_alex' => 'integer',
'tid_skin' => 'integer',
'tid_cape' => 'integer',
];
@ -63,18 +59,27 @@ class Player extends Model
return $this->belongsTo('App\Models\User', 'uid');
}
public function getTidSkinAttribute($value)
{
if ($value == -1) {
$this->tid_skin = $value = $this->preference == 'default'
? $this->tid_steve
: $this->tid_alex;
$this->save();
}
return $value;
}
/**
* Get specific texture of player.
*
* @param string $type "steve" or "alex" or "cape".
* @param string $type "skin" or "cape".
* @return string The sha256 hash of texture file.
*/
public function getTexture($type)
{
if ($type == "skin")
$type = ($this->getPreference() == "default") ? "steve" : "alex";
if (in_array($type, self::$models)) {
if (in_array($type, self::$types)) {
return Texture::find($this["tid_$type"])['hash'];
}
@ -89,8 +94,8 @@ class Player extends Model
*/
public function setTexture(Array $tids)
{
foreach (self::$models as $model) {
$property = "tid_$model";
foreach (self::$types as $type) {
$property = "tid_$type";
if (isset($tids[$property])) {
$this->$property = $tids[$property];
@ -113,8 +118,8 @@ class Player extends Model
*/
public function checkForInvalidTextures()
{
foreach (self::$models as $model) {
$property = "tid_$model";
foreach (self::$types as $type) {
$property = "tid_$type";
if (! Texture::find($this->$property)) {
// reset texture
@ -148,34 +153,6 @@ class Player extends Model
return $this;
}
/**
* Set preferred model for the player.
*
* @param string $type "slim" or "default".
* @return $this
*/
public function setPreference($type)
{
$this->update([
'preference' => $type,
'last_modified' => get_datetime_string()
]);
event(new PlayerProfileUpdated($this));
return $this;
}
/**
* Get model preference of the player.
*
* @return string
*/
public function getPreference()
{
return $this['preference'];
}
/**
* Rename the player.
*
@ -244,20 +221,19 @@ class Player extends Model
{
$json[($api_type == self::CSL_API) ? 'username' : 'player_name'] = $this->player_name;
$model = $this->getPreference();
$sec_model = ($model == 'default') ? 'slim' : 'default';
$texture = Texture::find($this->tid_skin);
$model = empty($texture) ? 'default' : ($texture->type === 'steve' ? 'default' : 'slim');
if ($api_type == self::USM_API) {
$json['last_update'] = strtotime($this->last_modified);
$json['model_preference'] = [$model, $sec_model];
$json['model_preference'] = [$model];
}
if ($this->getTexture('steve') || $this->getTexture('alex')) {
// Skins dict order by preference model
$json['skins'][$model] = $this->getTexture($model == "default" ? "steve" : "alex");
$json['skins'][$sec_model] = $this->getTexture($sec_model == "default" ? "steve" : "alex");
$skinHash = $this->getTexture('skin');
if ($model == 'slim') {
$json['skins']['slim'] = $skinHash;
}
$json['skins']['default'] = $skinHash;
$json['cape'] = $this->getTexture('cape');
return json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

View File

@ -46,14 +46,6 @@ class ValidatorExtendServiceProvider extends ServiceProvider
return preg_match($regexp, $value);
});
Validator::extend('preference', function ($a, $value, $p, $v) {
return preg_match('/^(default|slim)$/', $value);
});
Validator::extend('model', function ($a, $value, $p, $v) {
return preg_match('/^(steve|alex|cape)$/', $value);
});
$this->registerExpiredRules();
}

View File

@ -13,7 +13,8 @@
"devitek/yaml-translation": "^4.1.0",
"composer/semver": "^1.4",
"mews/captcha": "^2.2",
"guzzlehttp/guzzle": "^6.3"
"guzzlehttp/guzzle": "^6.3",
"doctrine/dbal": "^2.9"
},
"require-dev": {
"fzaninotto/faker": "~1.4",

233
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9dffd7e330ca90a80760f9e44a3c9c50",
"content-hash": "425b8e682534d97b800252cf1ec95152",
"packages": [
{
"name": "composer/semver",
@ -114,6 +114,237 @@
"description": "Add YAML file support to Laravel TranslationServiceProvider",
"time": "2017-09-28T12:37:17+00:00"
},
{
"name": "doctrine/cache",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "d768d58baee9a4862ca783840eca1b9add7a7f57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/d768d58baee9a4862ca783840eca1b9add7a7f57",
"reference": "d768d58baee9a4862ca783840eca1b9add7a7f57",
"shasum": ""
},
"require": {
"php": "~7.1"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
"alcaeus/mongo-php-adapter": "^1.1",
"doctrine/coding-standard": "^4.0",
"mongodb/mongodb": "^1.1",
"phpunit/phpunit": "^7.0",
"predis/predis": "~1.0"
},
"suggest": {
"alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "Caching library offering an object-oriented API for many cache backends",
"homepage": "https://www.doctrine-project.org",
"keywords": [
"cache",
"caching"
],
"time": "2018-08-21T18:01:43+00:00"
},
{
"name": "doctrine/dbal",
"version": "v2.9.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
"reference": "22800bd651c1d8d2a9719e2a3dc46d5108ebfcc9",
"shasum": ""
},
"require": {
"doctrine/cache": "^1.0",
"doctrine/event-manager": "^1.0",
"ext-pdo": "*",
"php": "^7.1"
},
"require-dev": {
"doctrine/coding-standard": "^5.0",
"jetbrains/phpstorm-stubs": "^2018.1.2",
"phpstan/phpstan": "^0.10.1",
"phpunit/phpunit": "^7.4",
"symfony/console": "^2.0.5|^3.0|^4.0",
"symfony/phpunit-bridge": "^3.4.5|^4.0.5"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"bin": [
"bin/doctrine-dbal"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.9.x-dev",
"dev-develop": "3.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\": "lib/Doctrine/DBAL"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
}
],
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
"keywords": [
"abstraction",
"database",
"dbal",
"mysql",
"persistence",
"pgsql",
"php",
"queryobject"
],
"time": "2018-12-31T03:27:51+00:00"
},
{
"name": "doctrine/event-manager",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/event-manager.git",
"reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3",
"reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3",
"shasum": ""
},
"require": {
"php": "^7.1"
},
"conflict": {
"doctrine/common": "<2.9@dev"
},
"require-dev": {
"doctrine/coding-standard": "^4.0",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "lib/Doctrine/Common"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"description": "Doctrine Event Manager component",
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
"keywords": [
"event",
"eventdispatcher",
"eventmanager"
],
"time": "2018-06-11T11:59:03+00:00"
},
{
"name": "doctrine/inflector",
"version": "v1.1.0",

View File

@ -6,16 +6,7 @@ $factory->define(Player::class, function (Faker\Generator $faker) {
return [
'uid' => factory(App\Models\User::class)->create()->uid,
'player_name' => $faker->firstName,
'preference' => 'default',
'last_modified' => $faker->dateTime
];
});
$factory->defineAs(Player::class, 'slim', function (Faker\Generator $faker) {
return [
'uid' => factory(App\Models\User::class)->create()->uid,
'player_name' => $faker->firstName,
'preference' => 'slim',
'tid_skin' => 0,
'last_modified' => $faker->dateTime
];
});

View File

@ -26,16 +26,7 @@ class AddRememberTokenToUsersTable extends Migration
public function down()
{
Schema::table('users', function (Blueprint $table) {
if (config('database.default') == 'sqlite') {
// Dropping columns from a SQLite database requires `doctrine/dbal` dependency.
// However, we won't install it because it's too hard to specify the version of
// all the new dependencies exactly to make them support PHP ^5.5.9. Damn it.
return;
}
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('remember_token');
});
$table->dropColumn('remember_token');
});
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTidSkin extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('players', function (Blueprint $table) {
$table->integer('tid_skin')->default(-1);
$table->string('preference', 10)->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('players', function (Blueprint $table) {
$table->dropColumn('tid_skin');
$table->string('preference', 10)->nullable(false)->change();
});
}
}

View File

@ -31,24 +31,12 @@
<i class="fas fa-edit btn-edit"></i>
</a>
</span>
<span v-else-if="props.column.field === 'preference'">
{{ props.formattedRow[props.column.field] }}
<a @click="togglePreference(props.row)" :title="$t('admin.changePreference')" data-test="preference">
<i class="fas fa-edit btn-edit"></i>
</a>
</span>
<span v-else-if="props.column.field === 'preview'">
<a
v-if="props.row.tid_steve"
:href="`${baseUrl}/skinlib/show/${props.row.tid_steve}`"
v-if="props.row.tid_skin"
:href="`${baseUrl}/skinlib/show/${props.row.tid_skin}`"
>
<img :src="`${baseUrl}/preview/64/${props.row.tid_steve}.png`" width="64">
</a>
<a
v-if="props.row.tid_alex"
:href="`${baseUrl}/skinlib/show/${props.row.tid_alex}`"
>
<img :src="`${baseUrl}/preview/64/${props.row.tid_alex}.png`" width="64">
<img :src="`${baseUrl}/preview/64/${props.row.tid_skin}.png`" width="64">
</a>
<a
v-if="props.row.tid_cape"
@ -95,9 +83,8 @@
<div class="modal-body">
<div class="form-group">
<label v-t="'admin.textureType'" />
<select class="form-control" v-model="textureChanges.model">
<option value="steve">Steve</option>
<option value="alex">Alex</option>
<select class="form-control" v-model="textureChanges.type">
<option value="skin" v-t="'general.skin'"></option>
<option value="cape" v-t="'general.cape'"></option>
</select>
</div>
@ -150,7 +137,6 @@ export default {
{ field: 'pid', label: 'PID', type: 'number' },
{ field: 'player_name', label: this.$t('general.player.player-name') },
{ field: 'uid', label: this.$t('general.player.owner'), type: 'number' },
{ field: 'preference', label: this.$t('general.player.preference'), globalSearchDisabled: true },
{ field: 'preview', label: this.$t('general.player.previews'), globalSearchDisabled: true, sortable: false },
{ field: 'last_modified', label: this.$t('general.player.last-modified') },
{ field: 'operations', label: this.$t('admin.operationsTitle'), globalSearchDisabled: true, sortable: false },
@ -178,7 +164,7 @@ export default {
},
textureChanges: {
originalIndex: -1,
model: 'steve',
type: 'skin',
tid: '',
}
};
@ -214,14 +200,14 @@ export default {
},
async changeTexture() {
const player = this.players[this.textureChanges.originalIndex];
const { model, tid } = this.textureChanges;
const { type, tid } = this.textureChanges;
const { errno, msg } = await this.$http.post(
'/admin/players?action=texture',
{ pid: player.pid, model, tid }
{ pid: player.pid, type, tid }
);
if (errno === 0) {
player[`tid_${model}`] = tid;
player[`tid_${type}`] = tid;
toastr.success(msg);
$('.modal').modal('hide');
} else {
@ -250,19 +236,6 @@ export default {
toastr.warning(msg);
}
},
async togglePreference(player) {
const preference = player.preference === 'default' ? 'slim' : 'default';
const { errno, msg } = await this.$http.post(
'/admin/players?action=preference',
{ pid: player.pid, preference }
);
if (errno === 0) {
player.preference = preference;
toastr.success(msg);
} else {
toastr.warning(msg);
}
},
async changeOwner(player) {
const { dismiss, value } = await swal({
text: this.$t('admin.changePlayerOwner'),

View File

@ -266,8 +266,7 @@ export default {
this.loadCloset(page);
},
avatarUrl(player) {
const tid = player.preference === 'default' ? player.tid_steve : player.tid_alex;
return `${blessing.base_url}/avatar/35/${tid}`;
return `${blessing.base_url}/avatar/35/${player.tid_skin}`;
},
async selectTexture(tid) {
const { type, hash } = await this.$http.get(`/skinlib/info/${tid}`);

View File

@ -9,16 +9,6 @@
<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>
@ -33,20 +23,12 @@
>
<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"
@ -104,36 +86,25 @@
<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}`">
{{ $t('general.skin') }}
<a v-if="preview2d.skin" :href="`${baseUrl}/skinlib/show/${preview2d.skin}`">
<img
class="skin2d"
:src="`${baseUrl}/preview/64/${preview2d.steve}.png`"
/>
</a>
<span v-else class="skin2d" v-t="'user.player.textures.empty'"></span>
<span v-else class="skin2d" v-t="'user.player.texture-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') }}
{{ $t('general.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>
<span v-else class="skin2d" v-t="'user.player.texture-empty'"></span>
</p>
</div>
</div><!-- /.box-body -->
@ -220,11 +191,7 @@
</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)
<input type="checkbox" v-model="clear.skin"> {{ $t('general.skin') }}
</label>
<br>
<label class="form-group">
@ -247,7 +214,6 @@
</template>
<script>
import SkinAlex from '../../images/textures/alex.png';
import { swal } from '../../js/notify';
import toastr from 'toastr';
@ -269,14 +235,12 @@ export default {
skinUrl: '',
capeUrl: '',
preview2d: {
steve: 0,
alex: 0,
skin: 0,
cape: 0
},
newPlayer: '',
clear: {
steve: false,
alex: false,
skin: false,
cape: false
},
playerNameRule: blessing.extra.rule,
@ -296,24 +260,14 @@ export default {
async preview(player) {
this.selected = player.pid;
this.preview2d.steve = player.tid_steve;
this.preview2d.alex = player.tid_alex;
this.preview2d.skin = player.tid_skin;
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 = '';
}
if (player.tid_skin) {
const skin = await this.$http.get(`/skinlib/info/${player.tid_skin}`);
this.skinUrl = `${this.baseUrl}/textures/${skin.hash}`;
} 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;
}
this.skinUrl = '';
}
if (player.tid_cape) {
const cape = await this.$http.get(`/skinlib/info/${player.tid_cape}`);
@ -322,19 +276,6 @@ export default {
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'),
@ -432,10 +373,6 @@ export default {
cursor: pointer;
border-bottom: 1px solid #f4f4f4;
#preference {
height: 31px;
}
.pid, .player-name {
padding-top: 13px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -49,7 +49,7 @@ test('update tables', async () => {
test('change texture', async () => {
window.$ = jest.fn(() => ({ modal() {} }));
Vue.prototype.$http.get.mockResolvedValue({ data: [
{ pid: 1, tid_steve: 0 }
{ pid: 1, tid_skin: 0 }
] });
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
@ -66,7 +66,7 @@ test('change texture', async () => {
await wrapper.vm.$nextTick();
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/players?action=texture',
{ pid: 1, model: 'steve', tid: 5 }
{ pid: 1, tid: 5, type: 'skin' }
);
button.trigger('click');
@ -108,33 +108,6 @@ test('change player name', async () => {
expect(wrapper.text()).toContain('new');
});
test('toggle preference', async () => {
Vue.prototype.$http.get.mockResolvedValue({ data: [
{ pid: 1, preference: 'default' }
] });
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1, msg: '1' })
.mockResolvedValue({ errno: 0, msg: '0' });
const wrapper = mount(Players);
await wrapper.vm.$nextTick();
const button = wrapper.find('[data-test="preference"]');
button.trigger('click');
expect(Vue.prototype.$http.post).toBeCalledWith(
'/admin/players?action=preference',
{ pid: 1, preference: 'slim' }
);
button.trigger('click');
await flushPromises();
expect(wrapper.text()).toContain('slim');
button.trigger('click');
await flushPromises();
expect(wrapper.text()).toContain('default');
});
test('change owner', async () => {
Vue.prototype.$http.get.mockResolvedValue({ data: [
{ pid: 1, uid: 2 }

View File

@ -152,8 +152,7 @@ test('compute avatar URL', () => {
Vue.prototype.$http.get.mockResolvedValue({});
const wrapper = mount(Closet);
const { avatarUrl } = wrapper.vm;
expect(avatarUrl({ preference: 'default', tid_steve: 1 })).toBe('/avatar/35/1');
expect(avatarUrl({ preference: 'slim', tid_alex: 1 })).toBe('/avatar/35/1');
expect(avatarUrl({ tid_skin: 1 })).toBe('/avatar/35/1');
});
test('select texture', async () => {
@ -191,7 +190,7 @@ test('apply texture', async () => {
.mockResolvedValueOnce({})
.mockResolvedValueOnce([])
.mockResolvedValueOnce([
{ pid: 1, player_name: 'name', preference: 'default', tid_steve: 10 }
{ pid: 1, player_name: 'name', tid_skin: 10 }
]);
const wrapper = mount(Closet);

View File

@ -28,10 +28,9 @@ test('fetch players data before mount', () => {
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 },
{ pid: 1, tid_skin: 1, tid_cape: 3 },
{ pid: 2, tid_skin: 0, tid_cape: 0 },
{ pid: 3, tid_skin: 2, tid_cape: 0 },
])
.mockResolvedValueOnce({ hash: 'a' })
.mockResolvedValueOnce({ hash: 'b' })
@ -51,35 +50,6 @@ test('click to preview player', async () => {
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 () => {
@ -167,7 +137,7 @@ 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');
expect(wrapper.text()).toContain('user.player.texture-empty');
});
test('add player', async () => {
@ -197,7 +167,7 @@ test('add player', async () => {
test('clear texture', async () => {
window.$ = jest.fn(() => ({ modal() {} }));
Vue.prototype.$http.get.mockResolvedValueOnce([
{ pid: 1, tid_steve: 1, tid_alex: 0, tid_cape: 0 }
{ pid: 1, tid_skin: 1, tid_cape: 0 }
]);
Vue.prototype.$http.post
.mockResolvedValueOnce({ errno: 1 })
@ -214,7 +184,7 @@ test('clear texture', async () => {
button.trigger('click');
expect(Vue.prototype.$http.post).toBeCalledWith(
'/user/player/texture/clear',
{ pid: 1, steve: true, alex: false, cape: false }
{ pid: 1, skin: true, cape: false }
);
button.trigger('click');

View File

@ -61,8 +61,6 @@ players:
no-permission: You have no permission to operate this player.
operations:
title: Operations
preference:
success: The preference of player ":player" has been changed to :preference
textures:
change: Change Textures
non-existent: No such texture tid.:tid

View File

@ -198,11 +198,7 @@ user:
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
texture-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:
@ -210,9 +206,6 @@ user:
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.
verification:
title: Verify Your Account
message: You must verify your email address before using the skin hosting service. Haven't received the email?
@ -255,7 +248,6 @@ admin:
changePlayerTexture: 'Change textures of :player'
changeTexture: Change Textures
changePlayerName: Change Player Name
changePreference: Toggle Preference
changeOwner: Change Owner
textureType: Texture Type
deletePlayer: Delete
@ -343,7 +335,6 @@ general:
player:
owner: Owner
player-name: Player Name
preference: Preference
previews: Texture Previews
last-modified: Last Modified

View File

@ -66,6 +66,5 @@ player:
pid: PID
owner: Owner
player-name: Player Name
preference: Preference
previews: Texture Previews
last-modified: Last Modified

View File

@ -83,14 +83,6 @@ 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
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.
@ -116,11 +108,6 @@ player:
clear:
success: The textures of player :name was resetted successfully.
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.
success: The preference of player :name was updated to :preference
profile:
avatar:
title: Change Avatar?

View File

@ -2,7 +2,6 @@
username: ':attribute format is invalid.'
player_name: 'The :attribute contains invalid character.'
no_special_chars: 'The :attribute must not contain special characters.'
preference: 'The :attribute must be default or slim.'
model: 'The :attribute must be steve, alex or cape.'
accepted: 'The :attribute must be accepted.'

View File

@ -61,8 +61,6 @@ players:
no-permission: 你无权操作此角色
operations:
title: 更多操作
preference:
success: 角色 :player 的优先模型已更改至 :preference
textures:
change: 更换材质
non-existent: 材质 tid.:tid 不存在

View File

@ -198,19 +198,12 @@ user:
add-player: 添加新角色
login-notice: 你现在可以使用你所拥有的角色名来登录皮肤站啦~
player-info: 角色信息 <small>(点击角色名以查看预览)</small>
textures:
steve: Steve 模型的皮肤:
alex: Alex 模型的皮肤:
cape: 披风:
empty: 未上传
texture-empty: 未上传
player-name-rule:
official: 角色名只能包含拉丁字母、数字以及下划线。
cjk: 角色名可使用拉丁字母、数字、下划线以及汉字(中日韩统一表意文字)。
custom: 本站使用了自定义的角色名规则,详情请咨询站点管理员。
player-name-length: 角色名最少要求 :min 个字符,最多不超过 :max 个字符。
preference:
title: 优先模型
description: Default 即为默认的 Steve 模型(手臂 4 个像素宽Slim 即为手臂宽 3 个像素的 Alex 模型。
verification:
title: 验证你的邮箱地址
message: 你必须验证你的邮箱才能正常使用本站的皮肤托管等功能。没有收到验证邮件?
@ -251,7 +244,6 @@ admin:
changePlayerTexture: '更换角色 :player 的材质'
changeTexture: 更换材质
changePlayerName: 更改角色名
changePreference: 切换模型
changeOwner: 更换角色拥有者
deletePlayer: 删除
changePlayerOwner: 请输入此角色要让渡至的用户 UID
@ -337,7 +329,6 @@ general:
player:
owner: 拥有者
player-name: 角色名
preference: 优先模型
previews: 预览材质
last-modified: 修改时间

View File

@ -66,6 +66,5 @@ player:
pid: PID
owner: 拥有者
player-name: 角色名
preference: 优先模型
previews: 预览材质
last-modified: 修改时间

View File

@ -117,11 +117,6 @@ player:
clear:
success: 角色 :name 的材质已被成功重置
preference:
title: 优先模型
description: Default 即为默认的 Steve 模型(手臂 4 个像素宽Slim 即为手臂宽 3 个像素的 Alex 模型。
success: 角色 :name 的优先模型已更改至 :preference
profile:
avatar:
title: 更改头像?

View File

@ -2,7 +2,6 @@
username: ':attribute 格式错误。'
player_name: ':attribute 不符合规则。'
no_special_chars: ':attribute 不能包含特殊字符。'
preference: ':attribute 的值必须为 default 或 slim。'
model: ':attribute 的值必须为 steve、alex 或 cape。'
accepted: ':attribute 必须接受。'
@ -89,7 +88,6 @@ attributes:
new_nickname: 新昵称
player_name: 角色名
new_player_name: 新角色名
preference: 优先模型
model: 模型
name: 名称
new_name: 新名称

View File

@ -21,7 +21,6 @@ Route::group(['middleware' => 'player'], function()
Route::get('/{api}/{player_name}.json', 'TextureController@jsonWithApi')->where('api', 'usm|csl');
// Legacy links
Route::get('/skin/{player_name}.png', 'TextureController@skin');
Route::get('/skin/{model}/{pname}.png', 'TextureController@skinWithModel');
Route::get('/cape/{player_name}.png', 'TextureController@cape');
});

View File

@ -63,7 +63,6 @@ Route::group(['middleware' => ['web', 'auth'], 'prefix' => 'user'], function ()
Route::get ('/list', 'PlayerController@listAll');
Route::post('/add', 'PlayerController@add');
Route::any ('/show', 'PlayerController@show');
Route::post('/preference', 'PlayerController@setPreference');
Route::post('/set', 'PlayerController@setTexture');
Route::post('/texture/clear', 'PlayerController@clearTexture');
Route::post('/rename', 'PlayerController@rename');

View File

@ -200,9 +200,7 @@ class AdminControllerTest extends BrowserKitTestCase
'pid',
'uid',
'player_name',
'preference',
'tid_steve',
'tid_alex',
'tid_skin',
'tid_cape',
'last_modified'
]]
@ -214,9 +212,7 @@ class AdminControllerTest extends BrowserKitTestCase
'pid' => $player->pid,
'uid' => $user->uid,
'player_name' => $player->player_name,
'preference' => $player->preference,
'tid_steve' => $player->tid_steve,
'tid_alex' => $player->tid_alex,
'tid_skin' => $player->tid_skin,
'tid_cape' => $player->tid_cape
]]
]);
@ -504,45 +500,7 @@ class AdminControllerTest extends BrowserKitTestCase
'msg' => trans('admin.users.operations.invalid')
]);
// Change preference without `preference` field
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'preference'
], [
'Accept' => 'application/json',
])->seeJson([
'errno' => 1,
'msg' => trans('validation.required', ['attribute' => 'preference'])
]);
// Change preference but neither `default` nor `slim`
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'preference',
'preference' => 'steve'
], [
'Accept' => 'application/json',
])->seeJson([
'errno' => 1,
'msg' => trans('validation.preference', ['attribute' => 'preference'])
]);
// Set preference successfully
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'preference',
'preference' => 'slim'
])->seeJson([
'errno' => 0,
'msg' => trans(
'admin.players.preference.success',
['player' => $player->player_name, 'preference' => 'slim']
)
]);
$player = Player::find($player->pid);
$this->assertEquals('slim', $player->preference);
// Change texture without `model` field
// Change texture without `type` field
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture'
@ -550,26 +508,14 @@ class AdminControllerTest extends BrowserKitTestCase
'Accept' => 'application/json',
])->seeJson([
'errno' => 1,
'msg' => trans('validation.required', ['attribute' => 'model'])
]);
// Change texture with invalid model name
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'slim'
], [
'Accept' => 'application/json',
])->seeJson([
'errno' => 1,
'msg' => trans('validation.model', ['attribute' => 'model'])
'msg' => trans('validation.required', ['attribute' => 'type'])
]);
// Change texture without `tid` field
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'steve'
'type' => 'skin'
], [
'Accept' => 'application/json',
])->seeJson([
@ -581,7 +527,7 @@ class AdminControllerTest extends BrowserKitTestCase
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'steve',
'type' => 'skin',
'tid' => 'string'
], [
'Accept' => 'application/json',
@ -594,48 +540,34 @@ class AdminControllerTest extends BrowserKitTestCase
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'steve',
'type' => 'skin',
'tid' => -1
])->seeJson([
'errno' => 1,
'msg' => trans('admin.players.textures.non-existent', ['tid' => -1])
]);
$steve = factory(Texture::class)->create();
$alex = factory(Texture::class, 'alex')->create();
$skin = factory(Texture::class)->create();
$cape = factory(Texture::class, 'cape')->create();
// Steve
// Skin
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'steve',
'tid' => $steve->tid
'type' => 'skin',
'tid' => $skin->tid
])->seeJson([
'errno' => 0,
'msg' => trans('admin.players.textures.success', ['player' => $player->player_name])
]);
$player = Player::find($player->pid);
$this->assertEquals($steve->tid, $player->tid_steve);
// Alex
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'alex',
'tid' => $alex->tid
])->seeJson([
'errno' => 0,
'msg' => trans('admin.players.textures.success', ['player' => $player->player_name])
]);
$player = Player::find($player->pid);
$this->assertEquals($alex->tid, $player->tid_alex);
$this->assertEquals($skin->tid, $player->tid_skin);
// Cape
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'cape',
'type' => 'cape',
'tid' => $cape->tid
])->seeJson([
'errno' => 0,
@ -648,32 +580,20 @@ class AdminControllerTest extends BrowserKitTestCase
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'steve',
'type' => 'skin',
'tid' => 0
])->seeJson([
'errno' => 0,
'msg' => trans('admin.players.textures.success', ['player' => $player->player_name])
]);
$player = Player::find($player->pid);
$this->assertEquals(0, $player->tid_steve);
$this->assertNotEquals(0, $player->tid_alex);
$this->assertEquals(0, $player->tid_skin);
$this->assertNotEquals(0, $player->tid_cape);
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'alex',
'tid' => 0
])->seeJson([
'errno' => 0,
'msg' => trans('admin.players.textures.success', ['player' => $player->player_name])
]);
$player = Player::find($player->pid);
$this->assertEquals(0, $player->tid_alex);
$this->assertNotEquals(0, $player->tid_cape);
$this->postJson('/admin/players', [
'pid' => $player->pid,
'action' => 'texture',
'model' => 'cape',
'type' => 'cape',
'tid' => 0
])->seeJson([
'errno' => 0,

View File

@ -0,0 +1,43 @@
<?php
namespace Tests;
use Faker;
use Artisan;
use App\Models\Player;
use App\Console\Commands\MigratePlayersTable;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class MigratePlayersTableTest extends TestCase
{
use DatabaseTransactions;
public function testExecute()
{
$faker = Faker\Factory::create();
for ($i = 1; $i <= 10; $i++) {
factory(Player::class)->create([
'tid_skin' => -1,
'preference' => $faker->randomElement(['default', 'slim']),
'tid_steve' => $faker->randomDigit(),
'tid_alex' => $faker->randomDigit(),
]);
}
Artisan::call('bs:migrate-v4:players-table');
Player::all()
->each(function (Player $player) {
if ($player->preference == 'default') {
$this->assertEquals($player->tid_steve, $player->tid_skin);
} else {
$this->assertEquals($player->tid_alex, $player->tid_skin);
}
});
$this->assertEquals(0, Artisan::call('bs:migrate-v4:players-table'));
}
}

View File

@ -14,19 +14,11 @@ class PlayerTest extends TestCase
public function testGetTexture()
{
$steve = factory(Texture::class)->create();
$alex = factory(Texture::class, 'alex')->create();
$player = factory(Player::class)->create([
'tid_steve' => $steve->tid,
'tid_alex' => $alex->tid
]);
$skin = factory(Texture::class)->create();
$player = factory(Player::class)->create(['tid_skin' => $skin->tid]);
$player = Player::find($player->pid);
$this->assertEquals($steve->hash, $player->getTexture('skin'));
$player->setPreference('slim');
$player = Player::find($player->pid);
$this->assertEquals($alex->hash, $player->getTexture('skin'));
$this->assertEquals($skin->hash, $player->getTexture('skin'));
$this->assertFalse($player->getTexture('invalid_type'));
}
@ -46,4 +38,23 @@ class PlayerTest extends TestCase
$this->expectsEvents(\App\Events\PlayerProfileUpdated::class);
$player->updateLastModified();
}
public function testGetTidSkinAttribute()
{
$player = factory(Player::class)->create([
'tid_skin' => -1,
'preference' => 'default',
'tid_steve' => 5,
]);
$this->assertEquals(5, $player->tid_skin);
$this->assertEquals(5, Player::find($player->pid)->tid_skin);
$player = factory(Player::class)->create([
'tid_skin' => -1,
'preference' => 'slim',
'tid_alex' => 6,
]);
$this->assertEquals(6, $player->tid_skin);
$this->assertEquals(6, Player::find($player->pid)->tid_skin);
}
}

View File

@ -30,20 +30,13 @@ class PlayerControllerTest extends TestCase
public function testListAll()
{
$user = factory(User::class)->create();
$default = factory(Player::class)->create(['uid' => $user->uid]);
$slim = factory(Player::class, 'slim')->create(['uid' => $user->uid]);
$player = factory(Player::class)->create(['uid' => $user->uid]);
$this->actingAs($user)
->get('/user/player/list')
->assertJson([
[
'pid' => $default->pid,
'player_name' => $default->player_name,
'preference' => $default->preference,
],
[
'pid' => $slim->pid,
'player_name' => $slim->player_name,
'preference' => $slim->preference,
'pid' => $player->pid,
'player_name' => $player->player_name,
]
]);
}
@ -109,7 +102,6 @@ class PlayerControllerTest extends TestCase
$this->assertNotNull($player);
$this->assertEquals($user->uid, $player->uid);
$this->assertEquals('角色名', $player->player_name);
$this->assertEquals('default', $player->preference);
$this->assertEquals(
$score - option('score_per_player'),
User::find($user->uid)->score
@ -233,42 +225,28 @@ class PlayerControllerTest extends TestCase
{
$player = factory(Player::class)->create();
$user = $player->user;
$steve = factory(Texture::class)->create();
$alex = factory(Texture::class, 'alex')->create();
$skin = factory(Texture::class)->create();
$cape = factory(Texture::class, 'cape')->create();
// Set a not-existed texture
$this->actAs($user)
->postJson('/user/player/set', [
'pid' => $player->pid,
'tid' => ['steve' => -1]
'tid' => ['skin' => -1]
])->assertJson([
'errno' => 6,
'msg' => trans('skinlib.un-existent')
]);
// Set for "alex" type
// Model preference should be automatically set to "slim"
// Set for "skin" type
$this->postJson('/user/player/set', [
'pid' => $player->pid,
'tid' => ['alex' => $alex->tid]
'tid' => ['skin' => $skin->tid]
])->assertJson([
'errno' => 0,
'msg' => trans('user.player.set.success', ['name' => $player->player_name])
]);
$this->expectsEvents(Events\PlayerProfileUpdated::class);
$this->assertEquals(Player::find($player->pid)->preference, 'slim');
$this->assertEquals($alex->tid, Player::find($player->pid)->tid_alex);
// Set for "steve" type
$this->postJson('/user/player/set', [
'pid' => $player->pid,
'tid' => ['steve' => $steve->tid]
])->assertJson([
'errno' => 0,
'msg' => trans('user.player.set.success', ['name' => $player->player_name])
]);
$this->assertEquals($steve->tid, Player::find($player->pid)->tid_steve);
$this->assertEquals($skin->tid, Player::find($player->pid)->tid_skin);
// Set for "cape" type
$this->postJson('/user/player/set', [
@ -283,28 +261,11 @@ class PlayerControllerTest extends TestCase
// Invalid texture type is acceptable
$this->postJson('/user/player/set', [
'pid' => $player->pid,
'tid' => ['nope' => $steve->tid] // TID must be valid
'tid' => ['nope' => $skin->tid] // TID must be valid
])->assertJson([
'errno' => 0,
'msg' => trans('user.player.set.success', ['name' => $player->player_name])
]);
// Should be OK if texture type does not match
$this->postJson('/user/player/set', [
'pid' => $player->pid,
'tid' => [
'steve' => $alex->tid,
'alex' => $cape->tid,
'cape' => $steve->tid
]
])->assertJson([
'errno' => 0,
'msg' => trans('user.player.set.success', ['name' => $player->player_name])
]);
$player = Player::find($player->pid);
$this->assertEquals($steve->tid, $player->tid_steve);
$this->assertEquals($alex->tid, $player->tid_alex);
$this->assertEquals($cape->tid, $player->tid_cape);
}
public function testClearTexture()
@ -313,9 +274,8 @@ class PlayerControllerTest extends TestCase
$user = $player->user;
$player->setTexture([
'tid_steve' => 1,
'tid_alex' => 2,
'tid_cape' => 3
'tid_skin' => 1,
'tid_cape' => 2
]);
$player = Player::find($player->pid);
@ -323,58 +283,14 @@ class PlayerControllerTest extends TestCase
$this->actAs($user)
->postJson('/user/player/texture/clear', [
'pid' => $player->pid,
'steve' => 1, // "1" stands for "true"
'alex' => 1,
'skin' => 1, // "1" stands for "true"
'cape' => 1,
'nope' => 1, // Invalid texture type is acceptable
])->assertJson([
'errno' => 0,
'msg' => trans('user.player.clear.success', ['name' => $player->player_name])
]);
$this->assertEquals(0, Player::find($player->pid)->tid_steve);
$this->assertEquals(0, Player::find($player->pid)->tid_alex);
$this->assertEquals(0, Player::find($player->pid)->tid_skin);
$this->assertEquals(0, Player::find($player->pid)->tid_cape);
}
public function testSetPreference()
{
// Without `preference` field
$player = factory(Player::class)->create();
$this->actAs($player->user)
->postJson('/user/player/preference', [
'pid' => $player->pid
], [
'X-Requested-With' => 'XMLHttpRequest'
])->assertJson([
'errno' => 1,
'msg' => trans('validation.required', ['attribute' => 'preference'])
]);
// value of `preference` is invalid
$this->postJson('/user/player/preference', [
'pid' => $player->pid,
'preference' => 'steve'
], [
'X-Requested-With' => 'XMLHttpRequest'
])->assertJson([
'errno' => 1,
'msg' => trans('validation.preference', ['attribute' => 'preference'])
]);
// Success
$this->expectsEvents(Events\PlayerProfileUpdated::class);
$this->postJson('/user/player/preference', [
'pid' => $player->pid,
'preference' => 'slim'
], [
'X-Requested-With' => 'XMLHttpRequest'
])->assertJson([
'errno' => 0,
'msg' => trans(
'user.player.preference.success',
['name' => $player->player_name, 'preference' => 'slim']
)
]);
$this->assertEquals('slim', Player::find($player->pid)->preference);
}
}

View File

@ -793,14 +793,14 @@ class SkinlibControllerTest extends TestCase
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$uploader->score += $texture->size * option('private_score_per_storage');
$uploader->save();
$player = factory(Player::class)->create(['tid_steve' => $texture->tid]);
$player = factory(Player::class)->create(['tid_skin' => $texture->tid]);
$this->postJson('/skinlib/privacy', ['tid' => $texture->tid])
->assertJson([
'errno' => 0,
'msg' => trans('skinlib.privacy.success', ['privacy' => trans('general.private')]),
'public' => false
]);
$this->assertEquals(0, Player::find($player->pid)->tid_steve);
$this->assertEquals(0, Player::find($player->pid)->tid_skin);
}
public function testRename()

View File

@ -26,7 +26,7 @@ class TextureControllerTest extends TestCase
->assertStatus(404);
// Player is banned
$player = factory(Player::class)->create(['tid_steve' => $steve->tid]);
$player = factory(Player::class)->create(['tid_skin' => $steve->tid]);
$player->user->setPermission(User::BANNED);
$this->get("/{$player->player_name}.json")
->assertSee(trans('general.player-banned'))
@ -39,8 +39,7 @@ class TextureControllerTest extends TestCase
->assertJson([
'username' => $player->player_name,
'skins' => [
'default' => $steve->hash,
'slim' => null
'default' => $steve->hash
],
'cape' => null
])->assertHeader('Last-Modified');
@ -49,15 +48,15 @@ class TextureControllerTest extends TestCase
public function testJsonWithApi()
{
$steve = factory(Texture::class)->create();
$player = factory(Player::class)->create(['tid_steve' => $steve->tid]);
$alex = factory(Texture::class, 'alex')->create();
$player = factory(Player::class)->create(['tid_skin' => $steve->tid]);
// CSL API
$this->getJson("/csl/{$player->player_name}.json")
->assertJson([
'username' => $player->player_name,
'skins' => [
'default' => $steve->hash,
'slim' => null
'default' => $steve->hash
],
'cape' => null
])->assertHeader('Last-Modified');
@ -66,13 +65,38 @@ class TextureControllerTest extends TestCase
$this->getJson("/usm/{$player->player_name}.json")
->assertJson([
'player_name' => $player->player_name,
'model_preference' => ['default', 'slim'],
'model_preference' => ['default'],
'skins' => [
'default' => $steve->hash,
'slim' => null
'default' => $steve->hash
],
'cape' => null
])->assertHeader('Last-Modified');
$player->tid_skin = $alex->tid;
$player->save();
// CSL API
$this->getJson("/csl/{$player->player_name}.json")
->assertJson([
'username' => $player->player_name,
'skins' => [
'default' => $alex->hash,
'slim' => $alex->hash
],
'cape' => null
]);
// USM API
$this->getJson("/usm/{$player->player_name}.json")
->assertJson([
'player_name' => $player->player_name,
'model_preference' => ['slim'],
'skins' => [
'default' => $alex->hash,
'slim' => $alex->hash
],
'cape' => null
]);
}
public function testTexture()
@ -115,46 +139,26 @@ class TextureControllerTest extends TestCase
public function testSkin()
{
$steve = factory(Texture::class)->create();
$player = factory(Player::class)->create([
'preference' => 'slim',
'tid_steve' => $steve->tid,
'tid_alex' => 0
]);
$skin = factory(Texture::class)->create();
$player = factory(Player::class)->create();
$this->get("/skin/{$player->player_name}.png")
->assertSee(trans('general.texture-not-uploaded', ['type' => 'alex']));
->assertSee(trans('general.texture-not-uploaded', ['type' => 'skin']));
$player->setPreference('default');
$player->tid_skin = $skin->tid;
$player->save();
$this->get("/skin/{$player->player_name}.png")
->assertSee(trans('general.texture-deleted'));
Storage::disk('textures')->put($steve->hash, '');
Storage::disk('textures')->put($skin->hash, '');
$this->get("/skin/{$player->player_name}.png")
->assertHeader('Content-Type', 'image/png')
->assertHeader('Last-Modified')
->assertHeader('Accept-Ranges', 'bytes')
->assertHeader('Content-Length', Storage::disk('textures')->size($steve->hash))
->assertHeader('Content-Length', Storage::disk('textures')->size($skin->hash))
->assertSuccessful();
}
public function testSkinWithModel()
{
$steve = factory(Texture::class)->create();
$player = factory(Player::class)->create([
'preference' => 'slim',
'tid_steve' => $steve->tid,
'tid_alex' => 0
]);
$this->get("/skin/alex/{$player->player_name}.png")
->assertSee(trans('general.texture-not-uploaded', ['type' => 'alex']));
$player->setPreference('default');
$this->get("/skin/steve/{$player->player_name}.png")
->assertSee(trans('general.texture-deleted'));
}
public function testCape()
{
$cape = factory(Texture::class, 'cape')->create();