Asset changes and bugfixes.
- Squashed a bug that prevented sales from being displayed on the transaction overview page. - Split up auto uploader and xml uploader on admin panel. - Manual asset uploader on admin panel. - General package AssetType support. - Manual BodyPart upload support. - Manual Face upload support. - Trusted, user creatable, and admin creatable options added to asset types. - Vertical tabbed navs. - Moved all routes for "shop.asset" to a method in the asset model itself. - Added a way to view a list of the most recent assets uploaded by admins. - Added Face render support. - Indev outfits page.
This commit is contained in:
parent
8b3f940fc6
commit
426778c340
|
|
@ -15,6 +15,7 @@ use App\Helpers\CdnHelper;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\AssetVersion;
|
use App\Models\AssetVersion;
|
||||||
use App\Models\RobloxAsset;
|
use App\Models\RobloxAsset;
|
||||||
|
use App\Models\UserAsset;
|
||||||
|
|
||||||
class AssetHelper
|
class AssetHelper
|
||||||
{
|
{
|
||||||
|
|
@ -24,23 +25,33 @@ class AssetHelper
|
||||||
'.ROBLOXSECURITY' => env('app.robloxcookie')
|
'.ROBLOXSECURITY' => env('app.robloxcookie')
|
||||||
], '.roblox.com');
|
], '.roblox.com');
|
||||||
|
|
||||||
return Http::withOptions(['cookies' => $cookieJar, 'headers' => ['User-Agent' => 'Roblox/WinInet']]);
|
return Http::withOptions(['cookies' => $cookieJar, 'headers' => ['User-Agent' => 'Roblox/WinInet', 'Requester' => 'Server']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function uploadRobloxAsset($id, $uploadToHolder = false)
|
public static function newAsset($props, $hash)
|
||||||
{
|
{
|
||||||
{
|
$asset = Asset::create($props);
|
||||||
$uploadedAsset = RobloxAsset::where('robloxAssetId', $id)->first();
|
$assetVersion = AssetVersion::create([
|
||||||
if($uploadedAsset)
|
'parentAsset' => $asset->id,
|
||||||
return $uploadedAsset->asset;
|
'localVersion' => 1,
|
||||||
}
|
'contentURL' => $hash
|
||||||
|
]);
|
||||||
|
$asset->assetVersionId = $assetVersion->id;
|
||||||
|
$asset->save();
|
||||||
|
|
||||||
$marketplaceResult = self::cookiedRequest()->get('https://api.roblox.com/marketplace/productinfo?assetId=' . $id);
|
UserAsset::createSerialed($asset->creatorId, $asset->id);
|
||||||
$assetResult = self::cookiedRequest()->get('https://assetdelivery.roblox.com/v2/asset?id=' . $id);
|
|
||||||
|
|
||||||
if(!$marketplaceResult->ok() || !$assetResult->ok())
|
return $asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getRBXMarketplaceInfo($assetId)
|
||||||
|
{
|
||||||
|
$marketplaceResult = self::cookiedRequest()->get('https://api.roblox.com/marketplace/productinfo?assetId=' . $assetId);
|
||||||
|
|
||||||
|
if(!$marketplaceResult->ok())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
$marketplaceResult = $marketplaceResult->json();
|
||||||
$assetTypeId = $marketplaceResult['AssetTypeId'];
|
$assetTypeId = $marketplaceResult['AssetTypeId'];
|
||||||
if(
|
if(
|
||||||
$assetTypeId == 41 || // Hair Accessory
|
$assetTypeId == 41 || // Hair Accessory
|
||||||
|
|
@ -54,25 +65,37 @@ class AssetHelper
|
||||||
$assetTypeId = 8;
|
$assetTypeId = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$marketplaceResult['AssetTypeId'] = $assetTypeId;
|
||||||
|
|
||||||
|
return $marketplaceResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function uploadRobloxAsset($id, $uploadToHolder = false)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
$uploadedAsset = RobloxAsset::where('robloxAssetId', $id)->first();
|
||||||
|
if($uploadedAsset)
|
||||||
|
return $uploadedAsset->asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
$marketplaceResult = self::getRBXMarketplaceInfo($id);
|
||||||
|
$assetResult = self::cookiedRequest()->get('https://assetdelivery.roblox.com/v2/asset?id=' . $id);
|
||||||
|
|
||||||
|
if(!$marketplaceResult || !$assetResult->ok())
|
||||||
|
return false;
|
||||||
|
|
||||||
$assetContent = Http::get($assetResult['locations'][0]['location']);
|
$assetContent = Http::get($assetResult['locations'][0]['location']);
|
||||||
$hash = CdnHelper::SaveContent($assetContent->body(), $assetContent->header('Content-Type'));
|
$hash = CdnHelper::SaveContent($assetContent->body(), $assetContent->header('Content-Type'));
|
||||||
$asset = Asset::create([
|
$asset = self::newAsset([
|
||||||
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
||||||
'name' => $marketplaceResult['Name'],
|
'name' => $marketplaceResult['Name'],
|
||||||
'description' => $marketplaceResult['Description'],
|
'description' => $marketplaceResult['Description'],
|
||||||
'approved' => true,
|
'approved' => true,
|
||||||
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
||||||
'onSale' => $marketplaceResult['IsForSale'],
|
'onSale' => $marketplaceResult['IsForSale'],
|
||||||
'assetTypeId' => $assetTypeId,
|
'assetTypeId' => $marketplaceResult['AssetTypeId'],
|
||||||
'assetVersionId' => 0
|
'assetVersionId' => 0
|
||||||
]);
|
], $hash);
|
||||||
$assetVersion = AssetVersion::create([
|
|
||||||
'parentAsset' => $asset->id,
|
|
||||||
'localVersion' => 1,
|
|
||||||
'contentURL' => $hash
|
|
||||||
]);
|
|
||||||
$asset->assetVersionId = $assetVersion->id;
|
|
||||||
$asset->save();
|
|
||||||
|
|
||||||
RobloxAsset::create([
|
RobloxAsset::create([
|
||||||
'robloxAssetId' => $id,
|
'robloxAssetId' => $id,
|
||||||
|
|
@ -90,42 +113,22 @@ class AssetHelper
|
||||||
return $uploadedAsset->asset;
|
return $uploadedAsset->asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
$marketplaceResult = self::cookiedRequest()->get('https://api.roblox.com/marketplace/productinfo?assetId=' . $id);
|
$marketplaceResult = self::getRBXMarketplaceInfo($id);
|
||||||
|
|
||||||
if(!$marketplaceResult->ok())
|
if(!$marketplaceResult)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$assetTypeId = $marketplaceResult['AssetTypeId'];
|
|
||||||
if(
|
|
||||||
$assetTypeId == 41 || // Hair Accessory
|
|
||||||
$assetTypeId == 42 || // Face Accessory
|
|
||||||
$assetTypeId == 43 || // Neck Accessory
|
|
||||||
$assetTypeId == 44 || // Shoulder Accessory
|
|
||||||
$assetTypeId == 45 || // Front Accessory
|
|
||||||
$assetTypeId == 46 || // Back Accessory
|
|
||||||
$assetTypeId == 47 // Waist Accessory
|
|
||||||
) {
|
|
||||||
$assetTypeId = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
$hash = CdnHelper::SaveContentB64($b64Content, 'application/octet-stream');
|
$hash = CdnHelper::SaveContentB64($b64Content, 'application/octet-stream');
|
||||||
$asset = Asset::create([
|
$asset = self::newAsset([
|
||||||
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
||||||
'name' => $marketplaceResult['Name'],
|
'name' => $marketplaceResult['Name'],
|
||||||
'description' => $marketplaceResult['Description'],
|
'description' => $marketplaceResult['Description'],
|
||||||
'approved' => true,
|
'approved' => true,
|
||||||
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
||||||
'onSale' => $marketplaceResult['IsForSale'],
|
'onSale' => $marketplaceResult['IsForSale'],
|
||||||
'assetTypeId' => $assetTypeId,
|
'assetTypeId' => $marketplaceResult['AssetTypeId'],
|
||||||
'assetVersionId' => 0
|
'assetVersionId' => 0
|
||||||
]);
|
], $hash);
|
||||||
$assetVersion = AssetVersion::create([
|
|
||||||
'parentAsset' => $asset->id,
|
|
||||||
'localVersion' => 1,
|
|
||||||
'contentURL' => $hash
|
|
||||||
]);
|
|
||||||
$asset->assetVersionId = $assetVersion->id;
|
|
||||||
$asset->save();
|
|
||||||
|
|
||||||
RobloxAsset::create([
|
RobloxAsset::create([
|
||||||
'robloxAssetId' => $id,
|
'robloxAssetId' => $id,
|
||||||
|
|
|
||||||
|
|
@ -144,10 +144,9 @@ class GridHelper
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getBodyColorsXML()
|
private static function getXMLFromGameDisk($fileName)
|
||||||
{
|
{
|
||||||
$disk = self::getGameDisk();
|
$disk = self::getGameDisk();
|
||||||
$fileName = 'BodyColors.xml';
|
|
||||||
|
|
||||||
if(!$disk->exists($fileName))
|
if(!$disk->exists($fileName))
|
||||||
throw new Exception('Unable to locate template file.');
|
throw new Exception('Unable to locate template file.');
|
||||||
|
|
@ -155,6 +154,21 @@ class GridHelper
|
||||||
return $disk->get($fileName);
|
return $disk->get($fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getBodyColorsXML()
|
||||||
|
{
|
||||||
|
return self::getXMLFromGameDisk('BodyColors.xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getBodyPartXML()
|
||||||
|
{
|
||||||
|
return self::getXMLFromGameDisk('BodyPart.xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getFaceXML()
|
||||||
|
{
|
||||||
|
return self::getXMLFromGameDisk('Face.xml');
|
||||||
|
}
|
||||||
|
|
||||||
public static function getArbiter($name)
|
public static function getArbiter($name)
|
||||||
{
|
{
|
||||||
$query = DynamicWebConfiguration::where('name', sprintf('%sArbiterIP', $name))->first();
|
$query = DynamicWebConfiguration::where('name', sprintf('%sArbiterIP', $name))->first();
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,18 @@
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
use App\Helpers\AssetHelper;
|
use App\Helpers\AssetHelper;
|
||||||
|
use App\Helpers\CdnHelper;
|
||||||
use App\Helpers\GridHelper;
|
use App\Helpers\GridHelper;
|
||||||
use App\Helpers\ValidationHelper;
|
use App\Helpers\ValidationHelper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\AppDeployment;
|
use App\Jobs\AppDeployment;
|
||||||
|
use App\Models\AssetType;
|
||||||
use App\Models\Deployment;
|
use App\Models\Deployment;
|
||||||
|
use App\Models\RobloxAsset;
|
||||||
use App\Rules\AppDeploymentFilenameRule;
|
use App\Rules\AppDeploymentFilenameRule;
|
||||||
|
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
|
|
@ -19,7 +23,173 @@ class AdminController extends Controller
|
||||||
|
|
||||||
|
|
||||||
// Admin+
|
// Admin+
|
||||||
|
function manualAssetUpload(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'asset-type-id' => ['required', 'int'],
|
||||||
|
'name' => ['required', 'string'],
|
||||||
|
'description' => ['string', 'nullable'],
|
||||||
|
'roblox-id' => ['int', 'min:0', 'nullable'],
|
||||||
|
'on-sale' => ['required', 'boolean'],
|
||||||
|
'price' => ['required_if:on-sale,true', 'int', 'min:0'],
|
||||||
|
'content' => ['nullable'],
|
||||||
|
'mesh-id' => ['int', 'nullable'],
|
||||||
|
'base-id' => ['int', 'nullable'],
|
||||||
|
'overlay-id' => ['int', 'nullable'],
|
||||||
|
],[
|
||||||
|
'asset-type-id.required' => 'An asset type ID must be provided.',
|
||||||
|
'roblox-id.integer' => 'Roblox ID must be an integer.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($validator->fails())
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
|
||||||
|
$valid = $validator->valid();
|
||||||
|
$isRobloxAsset = ($request->has('roblox-id') && $valid['roblox-id'] > 0);
|
||||||
|
|
||||||
|
if($isRobloxAsset)
|
||||||
|
{
|
||||||
|
$uploadedAsset = RobloxAsset::where('robloxAssetId', $valid['roblox-id'])->first();
|
||||||
|
if($uploadedAsset)
|
||||||
|
{
|
||||||
|
$validator->errors()->add('roblox-id', 'This asset has already been uploaded!');
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$assetType = AssetType::where('id', $valid['asset-type-id'])
|
||||||
|
->where('adminCreatable', 1)
|
||||||
|
->first();
|
||||||
|
if(!$assetType)
|
||||||
|
{
|
||||||
|
$validator->errors()->add('asset-type-id', 'Invalid asset type for admin upload.');
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$assetFunction = 'Unknown';
|
||||||
|
$assetFunctionArgs = [];
|
||||||
|
switch($assetType->id)
|
||||||
|
{
|
||||||
|
case 27: // Torso
|
||||||
|
case 28: // Right Arm
|
||||||
|
case 29: // Left Arm
|
||||||
|
case 30: // Left Leg
|
||||||
|
case 31: // Right Leg
|
||||||
|
$assetFunctionArgs = [$assetType->id, $valid];
|
||||||
|
$assetFunction = 'BodyPart';
|
||||||
|
break;
|
||||||
|
case 18: // Face
|
||||||
|
$assetFunctionArgs = [$validator, $valid];
|
||||||
|
$assetFunction = 'Face';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$assetFunctionArgs = [$validator];
|
||||||
|
$assetFunction = 'Generic';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$assetContent = $this->{ 'manualAssetUpload' . $assetFunction }($request, ...$assetFunctionArgs);
|
||||||
|
|
||||||
|
$hash = CdnHelper::SaveContent($assetContent, 'application/octet-stream');
|
||||||
|
$asset = AssetHelper::newAsset([
|
||||||
|
'creatorId' => 1,
|
||||||
|
'name' => $valid['name'],
|
||||||
|
'description' => $valid['description'],
|
||||||
|
'approved' => true,
|
||||||
|
'priceInTokens' => $valid['price'],
|
||||||
|
'onSale' => $valid['on-sale'] == 1 ? true : false,
|
||||||
|
'assetTypeId' => $assetType->id,
|
||||||
|
'assetVersionId' => 0
|
||||||
|
], $hash);
|
||||||
|
$asset->logAdminUpload(Auth::user()->id);
|
||||||
|
|
||||||
|
if($isRobloxAsset)
|
||||||
|
{
|
||||||
|
RobloxAsset::create([
|
||||||
|
'robloxAssetId' => $valid['roblox-id'],
|
||||||
|
'localAssetId' => $asset->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Your asset has been successfully uploaded!',
|
||||||
|
'assetId' => $asset->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function manualAssetUploadBodyPart(Request $request, $assetTypeId, $valid)
|
||||||
|
{
|
||||||
|
$bodyParts = [
|
||||||
|
27 => 1, // Torso
|
||||||
|
28 => 3, // Right Arm
|
||||||
|
29 => 2, // Left Arm
|
||||||
|
30 => 4, // Left Leg
|
||||||
|
31 => 5 // Right Leg
|
||||||
|
];
|
||||||
|
|
||||||
|
$document = simplexml_load_string(GridHelper::getBodyPartXML());
|
||||||
|
$document->xpath('//int[@name="BaseTextureId"]')[0][0] = $valid['base-id'] ?: 0;
|
||||||
|
$document->xpath('//token[@name="BodyPart"]')[0][0] = $bodyParts[$assetTypeId];
|
||||||
|
$document->xpath('//int[@name="MeshId"]')[0][0] = $valid['mesh-id'];
|
||||||
|
$document->xpath('//string[@name="Name"]')[0][0] = $valid['name'];
|
||||||
|
$document->xpath('//int[@name="OverlayTextureId"]')[0][0] = $valid['overlay-id'] ?: 0;
|
||||||
|
|
||||||
|
$domXML = dom_import_simplexml($document);
|
||||||
|
$assetContent = $domXML->ownerDocument->saveXML($domXML->ownerDocument->documentElement);
|
||||||
|
|
||||||
|
return $assetContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function manualAssetUploadFace(Request $request, $validator, $valid)
|
||||||
|
{
|
||||||
|
if(!$request->has('content'))
|
||||||
|
{
|
||||||
|
$validator->errors()->add('content', 'Asset content cannot be blank!');
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash = CdnHelper::SaveContent(
|
||||||
|
file_get_contents($request->file('content')->path()),
|
||||||
|
'application/octet-stream'
|
||||||
|
);
|
||||||
|
$imageAsset = AssetHelper::newAsset([
|
||||||
|
'creatorId' => 1,
|
||||||
|
'name' => $valid['name'],
|
||||||
|
'approved' => true,
|
||||||
|
'onSale' => false,
|
||||||
|
'assetTypeId' => 1, // Image
|
||||||
|
'assetVersionId' => 0
|
||||||
|
], $hash);
|
||||||
|
$imageAsset->logAdminUpload(Auth::user()->id);
|
||||||
|
$imageAssetUrl = route('client.asset', ['id' => $imageAsset->id]);
|
||||||
|
|
||||||
|
$document = simplexml_load_string(GridHelper::getFaceXML());
|
||||||
|
$document->xpath('//Content[@name="Texture"]')[0][0]->addChild('url', $imageAssetUrl);
|
||||||
|
|
||||||
|
$domXML = dom_import_simplexml($document);
|
||||||
|
$assetContent = $domXML->ownerDocument->saveXML($domXML->ownerDocument->documentElement);
|
||||||
|
|
||||||
|
return $assetContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function manualAssetUploadGeneric(Request $request, $validator)
|
||||||
|
{
|
||||||
|
if(!$request->has('content'))
|
||||||
|
{
|
||||||
|
$validator->errors()->add('content', 'Asset content cannot be blank!');
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$assetContent = file_get_contents($request->file('content')->path());
|
||||||
|
|
||||||
|
return $assetContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function manualAssetUploadUnknown(Request $request)
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
// Owner+
|
// Owner+
|
||||||
function deploy(Request $request)
|
function deploy(Request $request)
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ class AvatarController extends Controller
|
||||||
|
|
||||||
array_push($data, [
|
array_push($data, [
|
||||||
'id' => $asset->id,
|
'id' => $asset->id,
|
||||||
'Url' => route('shop.asset', ['asset' => $asset, 'assetName' => Str::slug($asset->name, '-')]),
|
'Url' => $asset->getShopUrl(),
|
||||||
'Thumbnail' => $asset->getThumbnail(),
|
'Thumbnail' => $asset->getThumbnail(),
|
||||||
'Name' => $asset->name,
|
'Name' => $asset->name,
|
||||||
'Wearing' => Auth::user()->isWearing($asset->id)
|
'Wearing' => Auth::user()->isWearing($asset->id)
|
||||||
|
|
@ -100,7 +100,7 @@ class AvatarController extends Controller
|
||||||
|
|
||||||
array_push($data, [
|
array_push($data, [
|
||||||
'id' => $asset->id,
|
'id' => $asset->id,
|
||||||
'Url' => route('shop.asset', ['asset' => $asset, 'assetName' => Str::slug($asset->name, '-')]),
|
'Url' => $asset->getShopUrl(),
|
||||||
'Thumbnail' => $asset->getThumbnail(),
|
'Thumbnail' => $asset->getThumbnail(),
|
||||||
'Name' => $asset->name,
|
'Name' => $asset->name,
|
||||||
'Wearing' => true
|
'Wearing' => true
|
||||||
|
|
@ -160,11 +160,7 @@ class AvatarController extends Controller
|
||||||
return ValidationHelper::generateValidatorError($validator);
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarAsset::Create([
|
Auth::user()->wearAsset($valid['id']);
|
||||||
'owner_id' => Auth::user()->id,
|
|
||||||
'asset_id' => $valid['id']
|
|
||||||
]);
|
|
||||||
|
|
||||||
Auth::user()->redraw();
|
Auth::user()->redraw();
|
||||||
|
|
||||||
return response(['success' => true]);
|
return response(['success' => true]);
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ class MoneyController extends Controller
|
||||||
|
|
||||||
foreach($dataPoint['Points'] as $transactionType)
|
foreach($dataPoint['Points'] as $transactionType)
|
||||||
{
|
{
|
||||||
$newColumn['total'] += Transaction::where('user_id', Auth::user()->id)
|
$column = $transactionType == 'Sales' ? 'seller_id' : 'user_id';
|
||||||
|
|
||||||
|
$newColumn['total'] += Transaction::where($column, Auth::user()->id)
|
||||||
->where('transaction_type_id', TransactionType::IDFromType($transactionType))
|
->where('transaction_type_id', TransactionType::IDFromType($transactionType))
|
||||||
->where(function($query) use($request) {
|
->where(function($query) use($request) {
|
||||||
if(!$request->has('filter'))
|
if(!$request->has('filter'))
|
||||||
|
|
@ -110,6 +112,7 @@ class MoneyController extends Controller
|
||||||
return $query->where('user_id', Auth::user()->id);
|
return $query->where('user_id', Auth::user()->id);
|
||||||
})
|
})
|
||||||
->where('transaction_type_id', $transactionType->id)
|
->where('transaction_type_id', $transactionType->id)
|
||||||
|
->with('asset')
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->cursorPaginate(30);
|
->cursorPaginate(30);
|
||||||
$prevCursor = $transactions->previousCursor();
|
$prevCursor = $transactions->previousCursor();
|
||||||
|
|
@ -126,7 +129,7 @@ class MoneyController extends Controller
|
||||||
$asset = null;
|
$asset = null;
|
||||||
if($transactionType->format != '')
|
if($transactionType->format != '')
|
||||||
$asset = [
|
$asset = [
|
||||||
'url' => route('shop.asset', ['asset' => $transaction->asset, 'assetName' => Str::slug($transaction->asset->name, '-')]),
|
'url' => $transaction->asset->getShopUrl(),
|
||||||
'name' => $transaction->asset->name
|
'name' => $transaction->asset->name
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ class ShopController extends Controller
|
||||||
'Thumbnail' => $asset->getThumbnail(),
|
'Thumbnail' => $asset->getThumbnail(),
|
||||||
'OnSale' => $asset->onSale,
|
'OnSale' => $asset->onSale,
|
||||||
'Price' => $asset->priceInTokens,
|
'Price' => $asset->priceInTokens,
|
||||||
'Url' => route('shop.asset', ['asset' => $asset->id, 'assetName' => Str::slug($asset->name, '-')])
|
'Url' => $asset->getShopUrl()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ class ThumbnailController extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} elseif($renderType == 'Asset') {
|
} elseif($renderType == 'Asset') {
|
||||||
|
if($model->renderId)
|
||||||
|
$model = Asset::where('id', $model->renderId)->first();
|
||||||
|
|
||||||
if($model->moderated)
|
if($model->moderated)
|
||||||
return response(['status' => 'success', 'data' => '/thumbs/DeletedThumbnail.png']);
|
return response(['status' => 'success', 'data' => '/thumbs/DeletedThumbnail.png']);
|
||||||
|
|
||||||
|
|
@ -108,13 +111,13 @@ class ThumbnailController extends Controller
|
||||||
if($renderType == 'User' && $valid['position'] == 'bust')
|
if($renderType == 'User' && $valid['position'] == 'bust')
|
||||||
$trackerType .= 'bust';
|
$trackerType .= 'bust';
|
||||||
$tracker = RenderTracker::where('type', $trackerType)
|
$tracker = RenderTracker::where('type', $trackerType)
|
||||||
->where('target', $valid['id'])
|
->where('target', $model->id)
|
||||||
->where('created_at', '>', Carbon::now()->subMinute());
|
->where('created_at', '>', Carbon::now()->subMinute());
|
||||||
|
|
||||||
if(!$tracker->exists()) {
|
if(!$tracker->exists()) {
|
||||||
$tracker = RenderTracker::create([
|
$tracker = RenderTracker::create([
|
||||||
'type' => $trackerType,
|
'type' => $trackerType,
|
||||||
'target' => $valid['id']
|
'target' => $model->id
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ArbiterRender::dispatch(
|
ArbiterRender::dispatch(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\AdminUpload;
|
||||||
use App\Models\DynamicWebConfiguration;
|
use App\Models\DynamicWebConfiguration;
|
||||||
use App\Models\PunishmentType;
|
use App\Models\PunishmentType;
|
||||||
use App\Models\Username;
|
use App\Models\Username;
|
||||||
|
|
@ -264,13 +265,30 @@ class AdminController extends Controller
|
||||||
return view('web.admin.userlookup')->with('users', $users)->with('input', $request->get('lookup'));
|
return view('web.admin.userlookup')->with('users', $users)->with('input', $request->get('lookup'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Admin+
|
||||||
|
|
||||||
// GET admin.autoupload
|
// GET admin.autoupload
|
||||||
function autoUpload()
|
function autoUpload()
|
||||||
{
|
{
|
||||||
return view('web.admin.autoupload');
|
return view('web.admin.catalog.autoupload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET admin.assetupload
|
||||||
|
function assetUpload()
|
||||||
|
{
|
||||||
|
return view('web.admin.catalog.assetupload');
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET admin.adminuploads
|
||||||
|
function getAdminUploads(Request $request)
|
||||||
|
{
|
||||||
|
$uploads = AdminUpload::query()
|
||||||
|
->orderByDesc('id')
|
||||||
|
->paginate(25);
|
||||||
|
|
||||||
|
return view('web.admin.catalog.adminuploads')->with('uploads', $uploads);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin+
|
|
||||||
function metricsVisualization()
|
function metricsVisualization()
|
||||||
{
|
{
|
||||||
return view('web.admin.metricsvisualization');
|
return view('web.admin.metricsvisualization');
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,9 @@ class ArbiterRender implements ShouldQueue
|
||||||
case 'Avatar':
|
case 'Avatar':
|
||||||
$arguments[0] = route('client.characterFetch', ['userId' => $this->assetId]);
|
$arguments[0] = route('client.characterFetch', ['userId' => $this->assetId]);
|
||||||
break;
|
break;
|
||||||
|
case 'Face':
|
||||||
|
$this->type = 'Decal';
|
||||||
|
break;
|
||||||
case 'Torso':
|
case 'Torso':
|
||||||
case 'Right Arm':
|
case 'Right Arm':
|
||||||
case 'Left Arm':
|
case 'Left Arm':
|
||||||
|
|
@ -116,8 +119,9 @@ class ArbiterRender implements ShouldQueue
|
||||||
break;
|
break;
|
||||||
case 'Package':
|
case 'Package':
|
||||||
// TODO: XlXi: Move these to config, as it could be different from prod in a testing environment. Also move these to their own assets (not loading from roblox).
|
// TODO: XlXi: Move these to config, as it could be different from prod in a testing environment. Also move these to their own assets (not loading from roblox).
|
||||||
|
$arguments[0] = $this->tracker->targetObj->getPackageAssetUrls();
|
||||||
array_push($arguments, 'https://www.roblox.com/asset/?id=1785197'); // Rig
|
array_push($arguments, 'https://www.roblox.com/asset/?id=1785197'); // Rig
|
||||||
array_push($arguments, '27113661;25251154'); // Custom Texture URLs (shirt and pands)
|
array_push($arguments, 'https://www.roblox.com/asset/?id=27113661;https://www.roblox.com/asset/?id=25251154'); // Custom Texture URLs (shirt and pands)
|
||||||
break;
|
break;
|
||||||
case 'Place':
|
case 'Place':
|
||||||
$arguments[2] = 768*4; // XlXi: These get scaled down by 4.
|
$arguments[2] = 768*4; // XlXi: These get scaled down by 4.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class AdminUpload extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'asset_id',
|
||||||
|
'uploader_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function asset()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Asset::class, 'asset_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -229,7 +229,9 @@ class User extends Authenticatable
|
||||||
|
|
||||||
foreach($oldHashes as $hash)
|
foreach($oldHashes as $hash)
|
||||||
{
|
{
|
||||||
if(!User::where('thumbnailBustHash', $hash)->orWhere('thumbnail2DHash', $hash)->orWhere('thumbnail3DHash', $hash)->exists())
|
$userThumbExists = User::where('thumbnailBustHash', $hash)->orWhere('thumbnail2DHash', $hash)->orWhere('thumbnail3DHash', $hash)->exists();
|
||||||
|
$assetThumbExists = Asset::where('thumbnail2DHash', $hash)->orWhere('thumbnail3DHash', $hash)->exists();
|
||||||
|
if(!$userThumbExists && !$assetThumbExists)
|
||||||
CdnHelper::Delete($hash);
|
CdnHelper::Delete($hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -247,6 +249,27 @@ class User extends Authenticatable
|
||||||
->whereRelation('asset', 'moderated', 0);
|
->whereRelation('asset', 'moderated', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function wearAsset($assetId)
|
||||||
|
{
|
||||||
|
$asset = Asset::where('id', $assetId)->first();
|
||||||
|
if($asset->assetType->id == 32)
|
||||||
|
{
|
||||||
|
foreach(explode(';', $asset->getPackageAssetIds()) as $asset)
|
||||||
|
{
|
||||||
|
if($this->isWearing($asset)) continue;
|
||||||
|
|
||||||
|
$this->wearAsset($asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AvatarAsset::Create([
|
||||||
|
'owner_id' => $this->id,
|
||||||
|
'asset_id' => $assetId
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getBodyColors()
|
public function getBodyColors()
|
||||||
{
|
{
|
||||||
$colors = AvatarColor::user($this->id);
|
$colors = AvatarColor::user($this->id);
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,34 @@ class UserAsset extends Model
|
||||||
return $this->belongsTo(Asset::class, 'asset_id');
|
return $this->belongsTo(Asset::class, 'asset_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function owner()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'owner_id');
|
||||||
|
}
|
||||||
|
|
||||||
public static function createSerialed($ownerId, $assetId)
|
public static function createSerialed($ownerId, $assetId)
|
||||||
{
|
{
|
||||||
return self::create([
|
$userAsset = self::create([
|
||||||
'owner_id' => $ownerId,
|
'owner_id' => $ownerId,
|
||||||
'asset_id' => $assetId,
|
'asset_id' => $assetId,
|
||||||
'serial' => self::where('asset_id', $assetId)->count()+1
|
'serial' => self::where('asset_id', $assetId)->count()+1
|
||||||
]);
|
]);
|
||||||
|
$userAsset->postSerialize();
|
||||||
|
|
||||||
|
return $userAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postSerialize()
|
||||||
|
{
|
||||||
|
// Grant package assets.
|
||||||
|
if($this->asset->assetType->id == 32)
|
||||||
|
{
|
||||||
|
foreach(explode(';', $this->asset->getPackageAssetIds()) as $asset)
|
||||||
|
{
|
||||||
|
if($this->owner->hasAsset($asset)) continue;
|
||||||
|
|
||||||
|
self::createSerialed($this->owner_id, $asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
use App\Helpers\CdnHelper;
|
||||||
use App\Models\AssetType;
|
use App\Models\AssetType;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -41,7 +43,7 @@ class Asset extends Model
|
||||||
'creatorId',
|
'creatorId',
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'parentAssetId',
|
'renderId',
|
||||||
'approved',
|
'approved',
|
||||||
'priceInTokens',
|
'priceInTokens',
|
||||||
'onSale',
|
'onSale',
|
||||||
|
|
@ -139,6 +141,12 @@ class Asset extends Model
|
||||||
|
|
||||||
public function getThumbnail()
|
public function getThumbnail()
|
||||||
{
|
{
|
||||||
|
if($this->renderId)
|
||||||
|
{
|
||||||
|
$asset = Asset::where('id', $this->renderId)->first();
|
||||||
|
return $asset->getThumbnail();
|
||||||
|
}
|
||||||
|
|
||||||
if($this->moderated)
|
if($this->moderated)
|
||||||
return '/thumbs/DeletedThumbnail.png';
|
return '/thumbs/DeletedThumbnail.png';
|
||||||
|
|
||||||
|
|
@ -160,6 +168,29 @@ class Asset extends Model
|
||||||
return route('content', $this->thumbnail2DHash);
|
return route('content', $this->thumbnail2DHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XlXi: For packages. https://abc.com/asset?id=1;https://abc.com/asset?id=2
|
||||||
|
public function getPackageAssetUrls()
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
$assets = $this->getPackageAssetIds();
|
||||||
|
if(!$assets)
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
foreach(explode(';', $assets) as $key=>$asset)
|
||||||
|
{
|
||||||
|
$result .= ($key > 0 ? ';' : '') . route('client.asset', ['id' => $asset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPackageAssetIds()
|
||||||
|
{
|
||||||
|
$disk = CdnHelper::GetDisk();
|
||||||
|
|
||||||
|
return $disk->get($this->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
public function set2DHash($hash)
|
public function set2DHash($hash)
|
||||||
{
|
{
|
||||||
$this->thumbnail2DHash = $hash;
|
$this->thumbnail2DHash = $hash;
|
||||||
|
|
@ -174,6 +205,19 @@ class Asset extends Model
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getShopUrl()
|
||||||
|
{
|
||||||
|
return route('shop.asset', ['asset' => $this, 'assetName' => Str::slug($this->name, '-')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logAdminUpload($uploaderId)
|
||||||
|
{
|
||||||
|
return AdminUpload::create([
|
||||||
|
'asset_id' => $this->id,
|
||||||
|
'uploader_id' => $uploaderId
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function getCreated()
|
public function getCreated()
|
||||||
{
|
{
|
||||||
$date = $this['created_at'];
|
$date = $this['created_at'];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components\Admin\Navigation;
|
||||||
|
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class AssetUploader extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('components.admin.navigation.asset-uploader');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ return new class extends Migration
|
||||||
$table->unsignedBigInteger('creatorId');
|
$table->unsignedBigInteger('creatorId');
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->longText('description')->nullable();
|
$table->longText('description')->nullable();
|
||||||
$table->unsignedBigInteger('parentAssetId')->nullable()->comment('Used by things like images that were created because of something else.');
|
$table->unsignedBigInteger('renderId')->nullable();
|
||||||
|
|
||||||
$table->boolean('approved')->default(false);
|
$table->boolean('approved')->default(false);
|
||||||
$table->boolean('moderated')->default(false);
|
$table->boolean('moderated')->default(false);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ return new class extends Migration
|
||||||
$table->boolean('copyable')->default(false); // Can be downloaded through /asset
|
$table->boolean('copyable')->default(false); // Can be downloaded through /asset
|
||||||
$table->boolean('sellable')->default(false); // If false, can be made on sale for free only.
|
$table->boolean('sellable')->default(false); // If false, can be made on sale for free only.
|
||||||
$table->boolean('locked')->default(false); // Cannot be put on sale
|
$table->boolean('locked')->default(false); // Cannot be put on sale
|
||||||
|
$table->boolean('trusted')->default(false); // Skips text filtering for name and description.
|
||||||
|
$table->boolean('userCreatable')->default(false); // Can be uploaded by a user.
|
||||||
|
$table->boolean('adminCreatable')->default(false); // Can be uploaded via the admin panel.
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('admin_uploads', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('asset_id');
|
||||||
|
$table->unsignedBigInteger('uploader_id');
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('admin_uploads');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -19,14 +19,17 @@ class AssetTypeSeeder extends Seeder
|
||||||
'name' => 'Image',
|
'name' => 'Image',
|
||||||
'renderable' => true,
|
'renderable' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'trusted' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'T-Shirt',
|
'name' => 'T-Shirt',
|
||||||
'renderable' => true,
|
'renderable' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Audio',
|
'name' => 'Audio',
|
||||||
|
|
@ -37,22 +40,26 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable' => true,
|
'renderable' => true,
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Lua',
|
'name' => 'Lua',
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'HTML',
|
'name' => 'HTML',
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Text',
|
'name' => 'Text',
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Hat',
|
'name' => 'Hat',
|
||||||
|
|
@ -60,15 +67,18 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Place',
|
'name' => 'Place',
|
||||||
'renderable' => true
|
'renderable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Model',
|
'name' => 'Model',
|
||||||
'renderable' => true
|
'renderable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Shirt',
|
'name' => 'Shirt',
|
||||||
|
|
@ -76,7 +86,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Pants',
|
'name' => 'Pants',
|
||||||
|
|
@ -84,7 +95,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Decal',
|
'name' => 'Decal',
|
||||||
|
|
@ -103,13 +115,16 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Face',
|
'name' => 'Face',
|
||||||
|
'renderable' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Gear',
|
'name' => 'Gear',
|
||||||
|
|
@ -117,7 +132,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
[
|
[
|
||||||
|
|
@ -129,28 +145,22 @@ class AssetTypeSeeder extends Seeder
|
||||||
'name' => 'Group Emblem',
|
'name' => 'Group Emblem',
|
||||||
'renderable' => true,
|
'renderable' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'trusted' => true
|
||||||
],
|
],
|
||||||
null,
|
null,
|
||||||
[
|
[
|
||||||
'name' => 'Animation',
|
'name' => 'Animation',
|
||||||
'copyable' => true
|
'copyable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Arms',
|
'name' => 'Arms',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'locked' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Legs',
|
'name' => 'Legs',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'locked' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Torso',
|
'name' => 'Torso',
|
||||||
|
|
@ -158,7 +168,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true,
|
'locked' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Right Arm',
|
'name' => 'Right Arm',
|
||||||
|
|
@ -166,7 +177,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true,
|
'locked' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Left Arm',
|
'name' => 'Left Arm',
|
||||||
|
|
@ -174,7 +186,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true,
|
'locked' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Left Leg',
|
'name' => 'Left Leg',
|
||||||
|
|
@ -182,7 +195,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true,
|
'locked' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Right Leg',
|
'name' => 'Right Leg',
|
||||||
|
|
@ -190,7 +204,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true,
|
'locked' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Package',
|
'name' => 'Package',
|
||||||
|
|
@ -198,7 +213,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
'renderable3d' => true,
|
'renderable3d' => true,
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'YouTubeVideo',
|
'name' => 'YouTubeVideo',
|
||||||
|
|
@ -206,7 +222,8 @@ class AssetTypeSeeder extends Seeder
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Game Pass',
|
'name' => 'Game Pass',
|
||||||
'sellable' => true
|
'sellable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'App',
|
'name' => 'App',
|
||||||
|
|
@ -216,11 +233,13 @@ class AssetTypeSeeder extends Seeder
|
||||||
[
|
[
|
||||||
'name' => 'Code',
|
'name' => 'Code',
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'locked' => true
|
'locked' => true,
|
||||||
|
'adminCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Plugin',
|
'name' => 'Plugin',
|
||||||
'sellable' => true
|
'sellable' => true,
|
||||||
|
'userCreatable' => true
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'SolidModel',
|
'name' => 'SolidModel',
|
||||||
|
|
@ -236,59 +255,31 @@ class AssetTypeSeeder extends Seeder
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Hair Accessory',
|
'name' => 'Hair Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Face Accessory',
|
'name' => 'Face Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Neck Accessory',
|
'name' => 'Neck Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Shoulder Accessory',
|
'name' => 'Shoulder Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Front Accessory',
|
'name' => 'Front Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Back Accessory',
|
'name' => 'Back Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Waist Accessory',
|
'name' => 'Waist Accessory',
|
||||||
'renderable' => true,
|
'locked' => true
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
|
||||||
'sellable' => true,
|
|
||||||
'wearable' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Climb Animation',
|
'name' => 'Climb Animation',
|
||||||
|
|
|
||||||
|
|
@ -408,7 +408,12 @@ class OutfitsTab extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (<><p>outfits</p></>);
|
return (<>
|
||||||
|
<div className="mb-1 d-flex">
|
||||||
|
<button className="btn btn-sm btn-primary ms-auto">Create New</button>
|
||||||
|
</div>
|
||||||
|
<p>outfits</p>
|
||||||
|
</>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,401 @@
|
||||||
|
/*
|
||||||
|
Copyright © XlXi 2022
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, createElement, createRef } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import classNames from 'classnames/bind';
|
||||||
|
|
||||||
|
import { buildGenericApiUrl } from '../util/HTTP.js';
|
||||||
|
import Loader from './Loader';
|
||||||
|
|
||||||
|
axios.defaults.withCredentials = true;
|
||||||
|
|
||||||
|
const assetTypes = [
|
||||||
|
{
|
||||||
|
assetTypeId: 1,
|
||||||
|
name: 'Image',
|
||||||
|
type: 'fileupload',
|
||||||
|
extra: <p>PNG, JPG, JPEG, and other common image formats are supported.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 4,
|
||||||
|
name: 'Mesh',
|
||||||
|
type: 'fileupload',
|
||||||
|
extra: <p>Your mesh can be <b>obj</b> or Roblox's <b>mesh</b> format.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 5,
|
||||||
|
name: 'Lua',
|
||||||
|
type: 'text',
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 6,
|
||||||
|
name: 'HTML',
|
||||||
|
type: 'text',
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 7,
|
||||||
|
name: 'Text',
|
||||||
|
type: 'text',
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 8,
|
||||||
|
name: 'Hat',
|
||||||
|
type: 'fileupload',
|
||||||
|
sellable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 17,
|
||||||
|
name: 'Head',
|
||||||
|
type: 'fileupload',
|
||||||
|
extra: <p>Heads are SpecialMeshes. Export it from studio, do not upload the mesh file here.</p>,
|
||||||
|
sellable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 18,
|
||||||
|
name: 'Face',
|
||||||
|
type: 'fileupload',
|
||||||
|
extra: <p>Faces are image files. The XML will be automatically generated.</p>,
|
||||||
|
sellable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 19,
|
||||||
|
name: 'Gear',
|
||||||
|
type: 'fileupload',
|
||||||
|
sellable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 27,
|
||||||
|
name: 'Torso',
|
||||||
|
type: 'packagepart',
|
||||||
|
extra: <p>Overlay ID displays atop clothing, base ID displays under clothing.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 28,
|
||||||
|
name: 'Right Arm',
|
||||||
|
type: 'packagepart',
|
||||||
|
extra: <p>Overlay ID displays atop clothing, base ID displays under clothing.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 29,
|
||||||
|
name: 'Left Arm',
|
||||||
|
type: 'packagepart',
|
||||||
|
extra: <p>Overlay ID displays atop clothing, base ID displays under clothing.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 30,
|
||||||
|
name: 'Left Leg',
|
||||||
|
type: 'packagepart',
|
||||||
|
extra: <p>Overlay ID displays atop clothing, base ID displays under clothing.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 31,
|
||||||
|
name: 'Right Leg',
|
||||||
|
type: 'packagepart',
|
||||||
|
extra: <p>Overlay ID displays atop clothing, base ID displays under clothing.</p>,
|
||||||
|
sellable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 32,
|
||||||
|
name: 'Package',
|
||||||
|
type: 'text',
|
||||||
|
extra: <p>Asset IDs for package. Example: 1;2;3;4;5</p>,
|
||||||
|
sellable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetTypeId: 37,
|
||||||
|
name: 'Code',
|
||||||
|
type: 'text',
|
||||||
|
sellable: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class ManualAssetUploadModel extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
forSale: false,
|
||||||
|
requestInputs: {
|
||||||
|
name: 'New Asset',
|
||||||
|
description: this.props.TypeName + ' Asset',
|
||||||
|
'roblox-id': 0,
|
||||||
|
'on-sale': false,
|
||||||
|
price: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.forSaleRef = createRef();
|
||||||
|
|
||||||
|
this.pushInput = this.pushInput.bind(this);
|
||||||
|
this.updateContentInputFile = this.updateContentInputFile.bind(this);
|
||||||
|
this.updateContentInputText = this.updateContentInputText.bind(this);
|
||||||
|
this.updateInput = this.updateInput.bind(this);
|
||||||
|
this.uploadAsset = this.uploadAsset.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.pushInput('asset-type-id', this.props.TypeId);
|
||||||
|
|
||||||
|
if(this.props.UploadType == 'packagepart')
|
||||||
|
{
|
||||||
|
this.pushInput('mesh-id', 0);
|
||||||
|
this.pushInput('overlay-id', 0);
|
||||||
|
this.pushInput('base-id', 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pushInput(name, value) {
|
||||||
|
this.setState((state, props) => ({
|
||||||
|
requestInputs: { ...state.requestInputs, [name]: value }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContentInputFile(value) {
|
||||||
|
this.pushInput('content', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContentInputText(value) {
|
||||||
|
const file = (value != '' && new Blob([ value ], { type: 'text/plain' }));
|
||||||
|
this.pushInput('content', file);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInput(e, prop='value') {
|
||||||
|
this.pushInput(e.target.id, e.target[prop]);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadAsset() {
|
||||||
|
let { requestInputs } = this.state;
|
||||||
|
|
||||||
|
this.props.setLoad(true);
|
||||||
|
|
||||||
|
if(!requestInputs.content && this.props.UploadType != 'packagepart')
|
||||||
|
{
|
||||||
|
this.props.flashError('Asset content cannot be blank!');
|
||||||
|
this.props.setLoad(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bodyFormData = new FormData();
|
||||||
|
Object.keys(requestInputs).map(key => {
|
||||||
|
let val = requestInputs[key];
|
||||||
|
if(typeof(val) == 'boolean')
|
||||||
|
val = val ? 1 : 0;
|
||||||
|
|
||||||
|
bodyFormData.append(key, val);
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.post(buildGenericApiUrl('api', 'admin/v1/manual-asset-upload'), bodyFormData)
|
||||||
|
.then(res => {
|
||||||
|
const data = res.data;
|
||||||
|
|
||||||
|
this.props.flashSuccess(data.message, data.assetId);
|
||||||
|
this.props.setLoad(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
const data = err.response.data;
|
||||||
|
|
||||||
|
this.props.flashError(data.errors ? data.errors[0].message : 'An unknown error occurred.');
|
||||||
|
this.props.setLoad(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { TypeName, UploadType, Extra, Sellable } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h4>Create a { TypeName }</h4>
|
||||||
|
<div>
|
||||||
|
<div className="mb-3">
|
||||||
|
{ Extra }
|
||||||
|
{
|
||||||
|
UploadType == 'fileupload'
|
||||||
|
?
|
||||||
|
<>
|
||||||
|
<label for="content" className="form-label">Find your { TypeName }:</label>
|
||||||
|
<input className="form-control mb-2" type="file" id="content" onChange={ (e) => this.updateContentInputFile(e.target.files[0]) } />
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
UploadType == 'packagepart'
|
||||||
|
?
|
||||||
|
<>
|
||||||
|
<label for="mesh-id" className="form-label">Mesh ID:</label>
|
||||||
|
<input className="form-control mb-2" type="number" id="mesh-id" value={ this.state.requestInputs['mesh-id'] } onChange={ this.updateInput } />
|
||||||
|
<label for="overlay-id" className="form-label">Overlay Texture ID (if applicable):</label>
|
||||||
|
<input className="form-control mb-2" type="number" id="overlay-id" value={ this.state.requestInputs['overlay-id'] } onChange={ this.updateInput } />
|
||||||
|
<label for="base-id" className="form-label">Base Texture ID (if applicable):</label>
|
||||||
|
<input className="form-control mb-2" type="number" id="base-id" value={ this.state.requestInputs['base-id'] } onChange={ this.updateInput } />
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
UploadType == 'text'
|
||||||
|
&&
|
||||||
|
<>
|
||||||
|
<label for="content" className="form-label">{ TypeName } Contents:</label>
|
||||||
|
<textarea className="form-control mb-2" type="text" id="content" onChange={ (e) => this.updateContentInputText(e.target.value) }></textarea>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
<label for="name" className="form-label">{ TypeName } Name:</label>
|
||||||
|
<input className="form-control mb-2" type="text" id="name" value={ this.state.requestInputs.name } onChange={ this.updateInput } />
|
||||||
|
<label for="description" className="form-label">{ TypeName } Description:</label>
|
||||||
|
<textarea className="form-control mb-2" type="text" id="description" value={ this.state.requestInputs.description } onChange={ this.updateInput }></textarea>
|
||||||
|
<label for="roblox-id" className="form-label">Roblox Asset ID (if applicable):</label>
|
||||||
|
<input className="form-control" type="number" id="roblox-id" value={ this.state.requestInputs['roblox-id'] } onChange={ this.updateInput } />
|
||||||
|
{
|
||||||
|
Sellable
|
||||||
|
&&
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
<h4>Sell this Item</h4>
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" value={ this.state.requestInputs['on-sale'] } id="on-sale" onChange={ (e) => this.updateInput(e, 'checked') } />
|
||||||
|
<label className="form-check-label" for="on-sale">Sell this Item</label>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
this.state.requestInputs['on-sale']
|
||||||
|
&&
|
||||||
|
<div className="input-group mb-2">
|
||||||
|
<span className="input-group-text px-1 pe-0">
|
||||||
|
<p className="virtubrick-tokens"> </p>
|
||||||
|
</span>
|
||||||
|
<input className="form-control" type="number" id="price" value={ this.state.requestInputs.price } min="0" max="999999999" placeholder="Price" onChange={ this.updateInput } />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-success px-5" onClick={ this.uploadAsset }>Upload</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ManualAssetUpload extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
createModelLoaded: false,
|
||||||
|
currentTabTypeId: 0,
|
||||||
|
tabKey: 0,
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.findAssetType = this.findAssetType.bind(this);
|
||||||
|
this.setLoad = this.setLoad.bind(this);
|
||||||
|
this.flashError = this.flashError.bind(this);
|
||||||
|
this.flashSuccess = this.flashSuccess.bind(this);
|
||||||
|
this.navigateAssetType = this.navigateAssetType.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount()
|
||||||
|
{
|
||||||
|
this.navigateAssetType(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
findAssetType(typeId) {
|
||||||
|
return assetTypes.find(obj => {
|
||||||
|
return obj.assetTypeId === typeId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoad(loading) {
|
||||||
|
this.setState({ loading: loading });
|
||||||
|
}
|
||||||
|
|
||||||
|
flashError(message) {
|
||||||
|
this.setState({ errorMessage: message });
|
||||||
|
setTimeout(function(){
|
||||||
|
this.setState({ errorMessage: null });
|
||||||
|
}.bind(this), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
flashSuccess(message, assetId) {
|
||||||
|
this.setState({ successMessage: message, successId: assetId });
|
||||||
|
setTimeout(function(){
|
||||||
|
this.setState({ successMessage: null, successId: null });
|
||||||
|
}.bind(this), 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateAssetType(typeId)
|
||||||
|
{
|
||||||
|
if(this.state.loading) return;
|
||||||
|
|
||||||
|
let data = this.findAssetType(typeId);
|
||||||
|
|
||||||
|
this.activeModel = createElement(
|
||||||
|
ManualAssetUploadModel,
|
||||||
|
{
|
||||||
|
TypeId: data.assetTypeId,
|
||||||
|
TypeName: data.name,
|
||||||
|
UploadType: data.type,
|
||||||
|
Extra: data.extra,
|
||||||
|
Sellable: data.sellable,
|
||||||
|
setLoad: this.setLoad,
|
||||||
|
flashError: this.flashError,
|
||||||
|
flashSuccess: this.flashSuccess,
|
||||||
|
key: this.state.tabKey
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({ createModelLoaded: true, currentTabTypeId: data.assetTypeId, tabKey: this.state.tabKey+1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="col-2 pe-0">
|
||||||
|
<ul className="nav nav-tabs flex-column">
|
||||||
|
{
|
||||||
|
assetTypes.map(({ assetTypeId, name }) =>
|
||||||
|
<li className="nav-item">
|
||||||
|
<button className={classNames({ 'nav-link': true, 'active': (assetTypeId == this.state.currentTabTypeId) })} disabled={ this.state.loading } onClick={ () => this.navigateAssetType(assetTypeId) }>{ name }</button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="col-10 ps-0">
|
||||||
|
{
|
||||||
|
this.state.successMessage
|
||||||
|
&&
|
||||||
|
<div className="alert alert-success virtubrick-alert virtubrick-error-popup">{ this.state.successMessage } <a className="text-decoration-none" href={ buildGenericApiUrl('www', `shop/${ this.state.successId }`) }>Click Here</a></div>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.errorMessage
|
||||||
|
&&
|
||||||
|
<div className="alert alert-danger virtubrick-alert virtubrick-error-popup">{ this.state.errorMessage }</div>
|
||||||
|
}
|
||||||
|
<div className="card p-3 vb-card-navconnector">
|
||||||
|
{
|
||||||
|
this.state.loading
|
||||||
|
&&
|
||||||
|
<div className="virtubrick-shop-overlay">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!this.state.createModelLoaded
|
||||||
|
?
|
||||||
|
<Loader />
|
||||||
|
:
|
||||||
|
this.activeModel
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ManualAssetUpload;
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright © XlXi 2023
|
||||||
|
*/
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
|
import ManualAssetUpload from '../components/ManualAssetUpload';
|
||||||
|
|
||||||
|
const assetUploadId = 'vb-manual-assetupload';
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (document.getElementById(assetUploadId)) {
|
||||||
|
render(<ManualAssetUpload />, document.getElementById(assetUploadId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1098,6 +1098,29 @@ input {
|
||||||
&.nav-justified > li {
|
&.nav-justified > li {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.flex-column {
|
||||||
|
.nav-link {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0.25rem;
|
||||||
|
|
||||||
|
&:not(.disabled):hover,
|
||||||
|
&:not(.disabled):focus,
|
||||||
|
&.active {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.vb-card-navconnector {
|
||||||
|
border-left: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes dropdownEase {
|
@keyframes dropdownEase {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<x-admin.navigation-tabs>
|
||||||
|
<x-admin.navigation-tab-link label="Upload Asset" :route="route('admin.assetupload')" />
|
||||||
|
<x-admin.navigation-tab-link label="Auto Uploader" :route="route('admin.autoupload')" />
|
||||||
|
<x-admin.navigation-tab-link label="Uploaded Assets" :route="route('admin.adminuploads')" />
|
||||||
|
</x-admin.navigation-tabs>
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
@extends('layouts.admin')
|
|
||||||
|
|
||||||
@section('title', 'Auto Uploader')
|
|
||||||
|
|
||||||
@push('content')
|
|
||||||
<div class="container-md">
|
|
||||||
<h4>Auto Uploader</h4>
|
|
||||||
<div class="card p-3">
|
|
||||||
<label for="vb-rbx-asset" class="form-label">Roblox Asset ID</label>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="Roblox Asset ID Here"
|
|
||||||
aria-label="Roblox Asset ID Here"
|
|
||||||
name="rbx-asset" id="vb-rbx-asset"
|
|
||||||
aria-describedby="vb-rbx-asset"
|
|
||||||
>
|
|
||||||
<button type="submit" class="btn btn-primary" type="button" name="rbx-asset-button" id="vb-rbx-asset-btn">Search</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endpush
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
@extends('layouts.admin')
|
||||||
|
|
||||||
|
@section('title', 'Uploaded Assets')
|
||||||
|
|
||||||
|
@push('content')
|
||||||
|
<div class="container-md">
|
||||||
|
<x-admin.navigation.asset-uploader />
|
||||||
|
<h4>Uploaded Assets</h4>
|
||||||
|
<div class="card">
|
||||||
|
@if(isset($uploads) && count($uploads) > 0)
|
||||||
|
<table class="table virtubrick-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Asset</th>
|
||||||
|
<th scope="col">Type</th>
|
||||||
|
<th scope="col">Uploader</th>
|
||||||
|
<th scope="col">Created</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($uploads as $upload)
|
||||||
|
@php
|
||||||
|
$asset = $upload->asset;
|
||||||
|
@endphp
|
||||||
|
<tr class="align-middle">
|
||||||
|
<th scope="col"><a href="{{ $asset->getShopUrl() }}" class="text-decoration-none">{{ $asset->name }}</th>
|
||||||
|
<th scope="col">{{ $asset->typeString() }}</th>
|
||||||
|
<th scope="col">
|
||||||
|
<a href="{{ route('admin.useradmin', ['ID' => $asset->user->id]) }}" class="text-decoration-none">
|
||||||
|
<x-user-circle :user="$asset->user" :size=40 />
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th scope="col">{{ $upload->created_at->isoFormat('l LT') }}</th>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ $uploads->links('pagination.virtubrick') }}
|
||||||
|
@else
|
||||||
|
<p class="text-muted p-3">No assets found.</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endpush
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
@extends('layouts.admin')
|
||||||
|
|
||||||
|
@section('title', 'Upload Asset')
|
||||||
|
|
||||||
|
@section('page-specific')
|
||||||
|
<!-- Secure Page JS -->
|
||||||
|
<script src="{{ mix('js/adm/ManualAssetUpload.js') }}"></script>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('content')
|
||||||
|
<div class="container-md">
|
||||||
|
<x-admin.navigation.asset-uploader />
|
||||||
|
<h4>Upload Asset</h4>
|
||||||
|
<div class="row" id="vb-manual-assetupload">
|
||||||
|
<x-loader />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endpush
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
@extends('layouts.admin')
|
||||||
|
|
||||||
|
@section('title', 'Auto Uploader')
|
||||||
|
|
||||||
|
@push('content')
|
||||||
|
<div class="container-md">
|
||||||
|
<x-admin.navigation.asset-uploader />
|
||||||
|
<h4>Auto Uploader</h4>
|
||||||
|
<div class="card p-3">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endpush
|
||||||
|
|
@ -44,7 +44,11 @@ Route::middleware('auth')->group(function () {
|
||||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||||
Route::middleware('roleset:owner')->group(function () {
|
Route::middleware('roleset:owner')->group(function () {
|
||||||
Route::get('/deploy', 'AdminController@deploy')->name('deploy');
|
Route::get('/deploy', 'AdminController@deploy')->name('deploy');
|
||||||
Route::post('/deploy/{version}', 'AdminController@deployVersion')->name('deploy');
|
Route::post('/deploy/{version}', 'AdminController@deployVersion')->name('deployVersion');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware('roleset:administrator')->group(function () {
|
||||||
|
Route::post('/manual-asset-upload', 'AdminController@manualAssetUpload')->name('manualAssetUpload')->middleware('throttle:6,2,adminassetupload');
|
||||||
});
|
});
|
||||||
|
|
||||||
// RCC Only
|
// RCC Only
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,15 @@ Route::group(['as' => 'admin.', 'prefix' => 'admin'], function() {
|
||||||
Route::get('/userlookuptool', 'AdminController@userLookup')->name('userlookup');
|
Route::get('/userlookuptool', 'AdminController@userLookup')->name('userlookup');
|
||||||
Route::post('/userlookuptool', 'AdminController@userLookupQuery')->name('userlookupquery');
|
Route::post('/userlookuptool', 'AdminController@userLookupQuery')->name('userlookupquery');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/auto-uploader', 'AdminController@autoUpload')->name('autoupload');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware('roleset:administrator')->group(function () {
|
Route::middleware('roleset:administrator')->group(function () {
|
||||||
|
Route::group(['prefix' => 'catalog'], function() {
|
||||||
|
Route::get('/autoassetupload', 'AdminController@autoUpload')->name('autoupload');
|
||||||
|
Route::get('/manualassetupload', 'AdminController@assetUpload')->name('assetupload');
|
||||||
|
Route::get('/uploadedassets', 'AdminController@getAdminUploads')->name('adminuploads');
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('/metrics', 'AdminController@metricsVisualization')->name('metricsvisualization');
|
Route::get('/metrics', 'AdminController@metricsVisualization')->name('metricsvisualization');
|
||||||
|
|
||||||
Route::get('/arbiter-diag/{arbiterType?}', 'AdminController@arbiterDiag')->name('diag');
|
Route::get('/arbiter-diag/{arbiterType?}', 'AdminController@arbiterDiag')->name('diag');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
||||||
|
<External>null</External>
|
||||||
|
<External>nil</External>
|
||||||
|
<Item class="CharacterMesh" referent="RBX0">
|
||||||
|
<Properties>
|
||||||
|
<int name="BaseTextureId"></int>
|
||||||
|
<token name="BodyPart"></token>
|
||||||
|
<int name="MeshId"></int>
|
||||||
|
<string name="Name"></string>
|
||||||
|
<int name="OverlayTextureId"></int>
|
||||||
|
<bool name="archivable">true</bool>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
||||||
|
<External>null</External>
|
||||||
|
<External>nil</External>
|
||||||
|
<Item class="Decal" referent="RBX0">
|
||||||
|
<Properties>
|
||||||
|
<token name="Face">5</token>
|
||||||
|
<string name="Name">face</string>
|
||||||
|
<float name="Shiny">20</float>
|
||||||
|
<float name="Specular">0</float>
|
||||||
|
<Content name="Texture"></Content>
|
||||||
|
<bool name="archivable">true</bool>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
|
|
@ -23,10 +23,6 @@
|
||||||
<category name="Private Servers">
|
<category name="Private Servers">
|
||||||
<link name="Private Server Transactions" route="admin.dashboard" />
|
<link name="Private Server Transactions" route="admin.dashboard" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category name="Catalog">
|
|
||||||
<link name="Auto Uploader" route="admin.autoupload" />
|
|
||||||
</category>
|
|
||||||
</list>
|
</list>
|
||||||
|
|
||||||
<list name="Administration" color="primary" roleset="Administrator">
|
<list name="Administration" color="primary" roleset="Administrator">
|
||||||
|
|
@ -73,6 +69,12 @@
|
||||||
<category name="Universe Status">
|
<category name="Universe Status">
|
||||||
<link name="Universe Status" route="admin.dashboard" />
|
<link name="Universe Status" route="admin.dashboard" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
|
<category name="Catalog">
|
||||||
|
<link name="Upload Asset" route="admin.assetupload" />
|
||||||
|
<link name="Auto Uploader" route="admin.autoupload" />
|
||||||
|
<link name="Uploaded Assets" route="admin.adminuploads" />
|
||||||
|
</category>
|
||||||
</list>
|
</list>
|
||||||
|
|
||||||
<list name="Owner" color="danger" roleset="Owner">
|
<list name="Owner" color="danger" roleset="Owner">
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ mix.js('resources/js/app.js', 'public/js')
|
||||||
.js('resources/js/pages/AvatarEditor.js', 'public/js')
|
.js('resources/js/pages/AvatarEditor.js', 'public/js')
|
||||||
.js('resources/js/pages/Item.js', 'public/js')
|
.js('resources/js/pages/Item.js', 'public/js')
|
||||||
.js('resources/js/pages/Place.js', 'public/js')
|
.js('resources/js/pages/Place.js', 'public/js')
|
||||||
|
.js('resources/js/pages/ManualAssetUpload.js', 'public/js/adm')
|
||||||
.js('resources/js/pages/ManualUserModeration.js', 'public/js/adm')
|
.js('resources/js/pages/ManualUserModeration.js', 'public/js/adm')
|
||||||
.js('resources/js/pages/AppDeployer.js', 'public/js/adm')
|
.js('resources/js/pages/AppDeployer.js', 'public/js/adm')
|
||||||
.js('resources/js/pages/SiteConfiguration.js', 'public/js/adm')
|
.js('resources/js/pages/SiteConfiguration.js', 'public/js/adm')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue