Updated JS copyright headers and added asset comments
This commit is contained in:
parent
8e0a66247d
commit
d8e0aaa50f
|
|
@ -0,0 +1,237 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
Graphictoria 2022
|
||||
This file handles communications between the arbiter and the website.
|
||||
*/
|
||||
|
||||
namespace App\Grid;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use SoapClient;
|
||||
|
||||
class SoapService
|
||||
{
|
||||
/**
|
||||
* SoapClient used by the functions in this class.
|
||||
*
|
||||
* @var SoapClient
|
||||
*/
|
||||
public $Client;
|
||||
|
||||
/**
|
||||
* Constructs the SoapClient.
|
||||
*
|
||||
* Arbiter address should be formatted like "http://127.0.0.1:64989"
|
||||
*
|
||||
* @param string $arbiterAddr
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($arbiterAddr) {
|
||||
$this->Client = new SoapClient(
|
||||
Storage::path('grid/RCCService.wsdl'),
|
||||
[
|
||||
'location' => $arbiterAddr,
|
||||
'uri' => 'http://roblox.com/',
|
||||
'exceptions' => false
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls on the soap service.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $args
|
||||
* @return null
|
||||
*/
|
||||
public function CallService($name, $args = []) {
|
||||
$soapResult = $this->Client->{$name}($args);
|
||||
|
||||
if(is_soap_fault($soapResult)) {
|
||||
// TODO: XlXi: log faults
|
||||
}
|
||||
|
||||
return $soapResult;
|
||||
}
|
||||
|
||||
/* Job constructors */
|
||||
|
||||
public static function LuaValue($value)
|
||||
{
|
||||
switch ($value) {
|
||||
case is_bool(json_encode($value)) || $value == 1:
|
||||
return json_encode($value);
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
public static function CastType($value)
|
||||
{
|
||||
$luaTypeConversions = [
|
||||
'NULL' => 'LUA_TNIL',
|
||||
'boolean' => 'LUA_TBOOLEAN',
|
||||
'integer' => 'LUA_TNUMBER',
|
||||
'double' => 'LUA_TNUMBER',
|
||||
'string' => 'LUA_TSTRING',
|
||||
'array' => 'LUA_TTABLE',
|
||||
'object' => 'LUA_TNIL'
|
||||
];
|
||||
return $luaTypeConversions[gettype($value)];
|
||||
}
|
||||
|
||||
public static function ToLuaArguments($luaArguments = [])
|
||||
{
|
||||
$luaValue = ['LuaValue' => []];
|
||||
|
||||
foreach ($luaArguments as $argument) {
|
||||
array_push(
|
||||
$luaValue['LuaValue'],
|
||||
[
|
||||
'type' => SoapService::CastType($argument),
|
||||
'value' => SoapService::LuaValue($argument)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $luaValue;
|
||||
}
|
||||
|
||||
public static function MakeJobJSON($jobID, $expiration, $category, $cores, $scriptName, $script, $scriptArgs = [])
|
||||
{
|
||||
return [
|
||||
'job' => [
|
||||
'id' => $jobID,
|
||||
'expirationInSeconds' => $expiration,
|
||||
'category' => $category,
|
||||
'cores' => $cores
|
||||
],
|
||||
'script' => [
|
||||
'name' => $scriptName,
|
||||
'script' => $script,
|
||||
'arguments' => SoapService::ToLuaArguments($scriptArgs)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/* Service functions */
|
||||
|
||||
public function HelloWorld()
|
||||
{
|
||||
return $this->CallService('HelloWorld');
|
||||
}
|
||||
|
||||
public function GetVersion()
|
||||
{
|
||||
return $this->CallService('GetVersion');
|
||||
}
|
||||
|
||||
public function GetStatus()
|
||||
{
|
||||
return $this->CallService('GetStatus');
|
||||
}
|
||||
|
||||
/* Job specific functions */
|
||||
|
||||
public function BatchJobEx($args)
|
||||
{
|
||||
return $this->CallService('BatchJobEx', $args);
|
||||
}
|
||||
|
||||
public function OpenJobEx($args)
|
||||
{
|
||||
return $this->CallService('OpenJobEx', $args);
|
||||
}
|
||||
|
||||
public function ExecuteEx($args)
|
||||
{
|
||||
return $this->CallService('ExecuteEx', $args);
|
||||
}
|
||||
|
||||
public function GetAllJobsEx()
|
||||
{
|
||||
return $this->CallService('GetAllJobsEx');
|
||||
}
|
||||
|
||||
public function CloseExpiredJobs()
|
||||
{
|
||||
return $this->CallService('CloseExpiredJobs');
|
||||
}
|
||||
|
||||
public function CloseAllJobs()
|
||||
{
|
||||
return $this->CallService('CloseAllJobs');
|
||||
}
|
||||
|
||||
/* Job management */
|
||||
|
||||
public function DiagEx($jobID, $type)
|
||||
{
|
||||
return $this->CallService(
|
||||
'DiagEx',
|
||||
[
|
||||
'type' => $type,
|
||||
'jobID' => $jobID
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function CloseJob($jobID)
|
||||
{
|
||||
return $this->CallService(
|
||||
'CloseJob',
|
||||
[
|
||||
'jobID' => $jobID
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function GetExpiration($jobID)
|
||||
{
|
||||
return $this->CallService(
|
||||
'GetExpiration',
|
||||
[
|
||||
'jobID' => $jobID
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function RenewLease($jobID, $expiration)
|
||||
{
|
||||
return $this->CallService(
|
||||
'RenewLease',
|
||||
[
|
||||
'jobID' => $jobID,
|
||||
'expirationInSeconds' => $expiration
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/* dep */
|
||||
|
||||
public function BatchJob($args)
|
||||
{
|
||||
return $this->BatchJobEx($args);
|
||||
}
|
||||
|
||||
public function OpenJob($args)
|
||||
{
|
||||
return $this->OpenJobEx($args);
|
||||
}
|
||||
|
||||
public function Execute($args)
|
||||
{
|
||||
return $this->ExecuteEx($args);
|
||||
}
|
||||
|
||||
public function GetAllJobs()
|
||||
{
|
||||
return $this->GetAllJobsEx();
|
||||
}
|
||||
|
||||
public function Diag($jobID, $type)
|
||||
{
|
||||
return $this->DiagEx($jobID, $type);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,23 +13,31 @@ use App\Models\DynamicWebConfiguration;
|
|||
|
||||
class GridHelper
|
||||
{
|
||||
public static function isIpWhitelisted() {
|
||||
public static function isIpWhitelisted()
|
||||
{
|
||||
$ip = request()->ip();
|
||||
$whitelistedIps = explode(';', DynamicWebConfiguration::where('name', 'WhitelistedIPs')->first()->value);
|
||||
|
||||
return in_array($ip, $whitelistedIps);
|
||||
}
|
||||
|
||||
public static function isAccessKeyValid() {
|
||||
public static function isAccessKeyValid()
|
||||
{
|
||||
$accessKey = DynamicWebConfiguration::where('name', 'ComputeServiceAccessKey')->first()->value;
|
||||
|
||||
return (request()->header('AccessKey') == $accessKey);
|
||||
}
|
||||
|
||||
public static function hasAllAccess() {
|
||||
public static function hasAllAccess()
|
||||
{
|
||||
if(app()->runningInConsole()) return true;
|
||||
if(GridHelper::isIpWhitelisted() && GridHelper::isAccessKeyValid()) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function createScript($scripts = [], $arguments = [])
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ use Illuminate\Validation\Validator;
|
|||
|
||||
class ValidationHelper
|
||||
{
|
||||
public static function generateValidatorError($validator) {
|
||||
return response(self::generateErrorJSON($validator), 400);
|
||||
}
|
||||
|
||||
public static function generateErrorJSON(Validator $validator) {
|
||||
$errorModel = [
|
||||
'errors' => []
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Helpers\ValidationHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Asset;
|
||||
use App\Models\Comment;
|
||||
|
||||
class CommentsController extends Controller
|
||||
{
|
||||
protected function listJson(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'assetId' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
|
||||
return $query->where('moderated', false);
|
||||
})
|
||||
]
|
||||
]);
|
||||
|
||||
if($validator->fails())
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
$comments = Comment::where('asset_id', $valid['assetId'])
|
||||
->where('deleted', false)
|
||||
->orderByDesc('id')
|
||||
->cursorPaginate(15);
|
||||
|
||||
$prevCursor = $comments->previousCursor();
|
||||
$nextCursor = $comments->nextCursor();
|
||||
|
||||
$result = [
|
||||
'data' => [],
|
||||
'prev_cursor' => ($prevCursor ? $prevCursor->encode() : null),
|
||||
'next_cursor' => ($nextCursor ? $nextCursor->encode() : null)
|
||||
];
|
||||
|
||||
foreach($comments as $comment) {
|
||||
// TODO: XlXi: user profile link
|
||||
// TODO: XlXi: user thumbnail
|
||||
$poster = [
|
||||
'name' => $comment->user->username,
|
||||
'thumbnail' => 'https://www.gtoria.local/images/testing/headshot.png',
|
||||
'url' => 'https://gtoria.local/todo123'
|
||||
];
|
||||
|
||||
$postDate = $comment['updated_at'];
|
||||
if(Carbon::now()->greaterThan($postDate->copy()->addDays(2)))
|
||||
$postDate = $postDate->isoFormat('lll');
|
||||
else
|
||||
$postDate = $postDate->calendar();
|
||||
|
||||
array_push($result['data'], [
|
||||
'commentId' => $comment->id,
|
||||
'poster' => $poster,
|
||||
'content' => $comment->content,
|
||||
'time' => $postDate
|
||||
]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function share(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'assetId' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
|
||||
return $query->where('moderated', false);
|
||||
})
|
||||
],
|
||||
'content' => ['required', 'max:200']
|
||||
]);
|
||||
|
||||
if($validator->fails())
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
$comment = new Comment();
|
||||
$comment->asset_id = $valid['assetId'];
|
||||
$comment->author_id = Auth::id();
|
||||
$comment->content = $valid['content'];
|
||||
$comment->save();
|
||||
|
||||
return response(['success' => true]);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ class FeedController extends Controller
|
|||
{
|
||||
// TODO: XlXi: Group shouts.
|
||||
$postsQuery = Shout::getPosts()
|
||||
->orderByDesc('created_at')
|
||||
->orderByDesc('id')
|
||||
->cursorPaginate(15);
|
||||
|
||||
/* */
|
||||
|
|
@ -38,10 +38,12 @@ class FeedController extends Controller
|
|||
if($post['poster_type'] == 'user') {
|
||||
$user = User::where('id', $post['poster_id'])->first();
|
||||
|
||||
// TODO: XlXi: user profile link
|
||||
$poster = [
|
||||
'type' => 'User',
|
||||
'name' => $user->username,
|
||||
'thumbnail' => 'https://www.gtoria.local/images/testing/headshot.png'
|
||||
'thumbnail' => 'https://www.gtoria.local/images/testing/headshot.png',
|
||||
'url' => 'https://gtoria.local/todo123'
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +51,7 @@ class FeedController extends Controller
|
|||
|
||||
$postDate = $post['updated_at'];
|
||||
if(Carbon::now()->greaterThan($postDate->copy()->addDays(2)))
|
||||
$postDate = $postDate->isoFormat('LLLL');
|
||||
$postDate = $postDate->isoFormat('lll');
|
||||
else
|
||||
$postDate = $postDate->calendar();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ class ShopController extends Controller
|
|||
'32' // Packages
|
||||
];
|
||||
|
||||
protected static function generateValidatorError($validator) {
|
||||
return response(ValidationHelper::generateErrorJSON($validator), 400);
|
||||
}
|
||||
|
||||
protected static function getAssets($assetTypeIds, $gearGenre=null)
|
||||
{
|
||||
// TODO: XlXi: Group owned assets
|
||||
|
|
@ -49,7 +45,7 @@ class ShopController extends Controller
|
|||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ShopController::generateValidatorError($validator);
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
|
@ -57,13 +53,13 @@ class ShopController extends Controller
|
|||
foreach(explode(',', $valid['assetTypeId']) as $assetTypeId) {
|
||||
if(!in_array($assetTypeId, $this->validAssetTypeIds)) {
|
||||
$validator->errors()->add('assetTypeId', 'Invalid assetTypeId supplied.');
|
||||
return ShopController::generateValidatorError($validator);
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
}
|
||||
|
||||
if($valid['assetTypeId'] != '19' && isset($valid['gearGenreId'])) {
|
||||
$validator->errors()->add('gearGenreId', 'gearGenreId can only be used with assetTypeId 19.');
|
||||
return ShopController::generateValidatorError($validator);
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
/* */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class Comment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'author_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -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('comments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('author_id');
|
||||
$table->longText('asset_id');
|
||||
$table->longText('content');
|
||||
$table->boolean('deleted')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('comments');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,15 +1,7 @@
|
|||
/**
|
||||
* First we will load all of this project's JavaScript dependencies which
|
||||
* includes React and other helpers. It's a great starting point while
|
||||
* building robust, powerful web applications using React + Laravel.
|
||||
*/
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
require('./bootstrap');
|
||||
|
||||
/**
|
||||
* Next, we will create a fresh React component instance and attach it to
|
||||
* the page. Then, you may begin adding components to this application
|
||||
* or customize the JavaScript scaffolding to fit your unique needs.
|
||||
*/
|
||||
|
||||
require('./components/Main');
|
||||
|
|
@ -1,18 +1,6 @@
|
|||
window._ = require('lodash');
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// window.Pusher = require('pusher-js');
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: process.env.MIX_PUSHER_APP_KEY,
|
||||
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
|
||||
// forceTLS: true
|
||||
// });
|
||||
window._ = require('lodash');
|
||||
|
|
@ -1,29 +1,185 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { Component } from 'react';
|
||||
import { Component, createRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import Twemoji from 'react-twemoji';
|
||||
|
||||
import { buildGenericApiUrl } from '../util/HTTP.js';
|
||||
import Loader from './Loader';
|
||||
|
||||
const commentsId = 'gt-comments'; // XlXi: Keep this in sync with the Item page.
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
class Comments extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
loadingCursor: false,
|
||||
disabled: false,
|
||||
error: '',
|
||||
comments: [],
|
||||
mouseHover: -1
|
||||
};
|
||||
|
||||
this.inputBox = createRef();
|
||||
|
||||
this.loadComments = this.loadComments.bind(this);
|
||||
this.loadMore = this.loadMore.bind(this);
|
||||
this.postComment = this.postComment.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
window.addEventListener('scroll', this.loadMore);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('scroll', this.loadMore);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let commentsElement = document.getElementById(commentsId);
|
||||
if (commentsElement) {
|
||||
this.assetId = commentsElement.getAttribute('data-asset-id')
|
||||
this.setState({
|
||||
canComment: (commentsElement.getAttribute('data-can-comment') === '1')
|
||||
});
|
||||
}
|
||||
|
||||
this.loadComments();
|
||||
}
|
||||
|
||||
loadComments() {
|
||||
axios.get(buildGenericApiUrl('api', `comments/v1/list-json?assetId=${this.assetId}`))
|
||||
.then(res => {
|
||||
const comments = res.data;
|
||||
|
||||
this.nextCursor = comments.next_cursor;
|
||||
this.setState({ comments: comments.data, loaded: true });
|
||||
});
|
||||
}
|
||||
|
||||
// XlXi: https://stackoverflow.com/questions/57778950/how-to-load-more-search-results-when-scrolling-down-the-page-in-react-js
|
||||
loadMore() {
|
||||
// XlXi: Taking the height of the footer into account.
|
||||
if (window.innerHeight + document.documentElement.scrollTop >= document.scrollingElement.scrollHeight-200) {
|
||||
if (!!(this.nextCursor) && !this.state.loadingCursor) {
|
||||
this.setState({ loadingCursor: true });
|
||||
|
||||
axios.get(buildGenericApiUrl('api', `comments/v1/list-json?assetId=${this.assetId}&cursor=${this.nextCursor}`))
|
||||
.then(res => {
|
||||
const comments = res.data;
|
||||
|
||||
this.nextCursor = comments.next_cursor;
|
||||
this.setState({ comments: this.state.comments.concat(comments.data), loadingCursor: false });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postComment() {
|
||||
this.setState({ disabled: true });
|
||||
|
||||
const postText = this.inputBox.current.value;
|
||||
if (postText == '') {
|
||||
this.setState({ disabled: false, error: 'Your comment cannot be blank.' });
|
||||
this.inputBox.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
axios.post(buildGenericApiUrl('api', `comments/v1/share`), { assetId: this.assetId, content: postText })
|
||||
.then(res => {
|
||||
this.inputBox.current.value = '';
|
||||
this.setState({ loaded: false, loadingCursor: false, disabled: false });
|
||||
this.loadComments();
|
||||
})
|
||||
.catch(err => {
|
||||
const data = err.response.data;
|
||||
|
||||
this.setState({ disabled: false, error: data.message });
|
||||
this.inputBox.current.focus();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<h4 className="pt-3">Comments</h4>
|
||||
{ /* TODO: XlXi: Hide comment input when logged out*/ }
|
||||
<div className="card mb-2">
|
||||
<div className="input-group p-2">
|
||||
<input disabled="disabled" type="text" className="form-control" placeholder="Write a comment!" />
|
||||
<button disabled="disabled" type="submit" className="btn btn-secondary">Share</button>
|
||||
{
|
||||
this.state.canComment != false
|
||||
?
|
||||
<div className="card mb-2">
|
||||
{
|
||||
this.state.error != ''
|
||||
?
|
||||
<div className="alert alert-danger graphictoria-alert graphictoria-error-popup m-2 mb-0">{ this.state.error }</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className="input-group p-2">
|
||||
<input disabled={ (!this.state.loaded || this.state.disabled) } type="text" className="form-control" placeholder="Write a comment!" ref={ this.inputBox }/>
|
||||
<button disabled={ (!this.state.loaded || this.state.disabled) } type="submit" className="btn btn-secondary" onClick={ this.postComment }>Post</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
this.state.loaded
|
||||
?
|
||||
(
|
||||
this.state.comments.length > 0
|
||||
?
|
||||
<>
|
||||
<div className="card">
|
||||
{
|
||||
this.state.comments.map(({ commentId, poster, time, content }, index) =>
|
||||
<>
|
||||
<div className="d-flex p-2" onMouseEnter={ () => this.setState({ mouseHover: index }) } onMouseLeave={ () => this.setState({ mouseHover: -1 }) }>
|
||||
<div className="me-2">
|
||||
<a href={ poster.url }>
|
||||
<img src={ poster.thumbnail } alt={ poster.name } width="50" height="50" className="border graphictora-feed-user-circle" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex-fill">
|
||||
<div className="d-flex">
|
||||
<a href={ poster.url } className="text-decoration-none me-auto fw-bold">{ poster.name }{ poster.icon ? <> <i className={ poster.icon }></i></> : null }</a>
|
||||
{ this.state.mouseHover == index ? <a href={ buildGenericApiUrl('www', `report/comment/${commentId}`) } target="_blank" className="text-decoration-none link-danger me-2">Report <i className="fa-solid fa-circle-exclamation"></i></a> : null }
|
||||
<p className="text-muted">{ time }</p>
|
||||
</div>
|
||||
<Twemoji options={{ className: 'twemoji', base: '/images/twemoji/', folder: 'svg', ext: '.svg' }}>
|
||||
<p>{ content }</p>
|
||||
</Twemoji>
|
||||
</div>
|
||||
</div>
|
||||
{ this.state.comments.length != (index+1) ? <hr className="m-0" /> : null }
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
this.state.loadingCursor ?
|
||||
<div className="d-flex mt-2">
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</>
|
||||
:
|
||||
<div className="text-center mt-3">
|
||||
<p className="text-muted">No comments were found. { this.state.canComment ? 'You could be the first!' : null }</p>
|
||||
</div>
|
||||
)
|
||||
:
|
||||
<div className="d-flex">
|
||||
<Loader />
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { Component, createRef } from 'react';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import Twemoji from 'react-twemoji';
|
||||
|
||||
import { buildGenericApiUrl } from '../util/HTTP.js';
|
||||
|
|
@ -124,7 +124,7 @@ class Feed extends Component {
|
|||
<>
|
||||
<div className="d-flex p-2" onMouseEnter={ () => this.setState({ mouseHover: index }) } onMouseLeave={ () => this.setState({ mouseHover: -1 }) }>
|
||||
<div className="me-2">
|
||||
<a href={ buildGenericApiUrl('www', (poster.type == 'User' ? `users/${poster.name}/profile` : `groups/${poster.id}`)) }>
|
||||
<a href={ poster.url }>
|
||||
{ poster.type == 'User' ?
|
||||
<img src={ poster.thumbnail } alt={ poster.name } width="50" height="50" className="border graphictora-feed-user-circle" /> :
|
||||
<img src={ poster.thumbnail } alt={ poster.name } width="50" height="50" className="img-fluid" />
|
||||
|
|
@ -133,8 +133,8 @@ class Feed extends Component {
|
|||
</div>
|
||||
<div className="flex-fill">
|
||||
<div className="d-flex">
|
||||
<a href={ buildGenericApiUrl('www', (poster.type == 'User' ? `users/${poster.name}/profile` : `groups/${poster.id}`)) } className="text-decoration-none fw-bold me-auto">{ poster.name }{ poster.icon ? <> <i className={ poster.icon }></i></> : null }</a>
|
||||
{ this.state.mouseHover == index ? <a href={ buildGenericApiUrl('www', `report/user-wall/${postId}`) } target="_blank" className="text-decoration-none link-danger me-2">Report <i className="fa-solid fa-circle-exclamation"></i></a> : null }
|
||||
<a href={ poster.url } className="text-decoration-none fw-bold me-auto">{ poster.name }{ poster.icon ? <> <i className={ poster.icon }></i></> : null }</a>
|
||||
{ this.state.mouseHover == index ? <a href={ buildGenericApiUrl('www', `report/wall/${postId}`) } target="_blank" className="text-decoration-none link-danger me-2">Report <i className="fa-solid fa-circle-exclamation"></i></a> : null }
|
||||
<p className="text-muted">{ time }</p>
|
||||
</div>
|
||||
<Twemoji options={{ className: 'twemoji', base: '/images/twemoji/', folder: 'svg', ext: '.svg' }}>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
const Loader = () => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import * as Bootstrap from 'bootstrap';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { createRef, Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { Component, createRef } from 'react';
|
||||
|
||||
|
|
@ -239,7 +241,7 @@ class Shop extends Component {
|
|||
navigateCategory(categoryId, data) {
|
||||
this.setState({selectedCategoryId: categoryId, pageLoaded: false});
|
||||
|
||||
let url = buildGenericApiUrl('api', 'catalog/v1/list-json');
|
||||
let url = buildGenericApiUrl('api', 'shop/v1/list-json');
|
||||
let paramIterator = 0;
|
||||
Object.keys(data).filter(key => {
|
||||
if (key == 'label')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
|
|
@ -10,7 +12,7 @@ import Comments from '../components/Comments';
|
|||
import PurchaseButton from '../components/PurchaseButton';
|
||||
|
||||
const purchaseId = 'gt-purchase-button';
|
||||
const commentsId = 'gt-comments';
|
||||
const commentsId = 'gt-comments'; // XlXi: Keep this in sync with the Comments component.
|
||||
|
||||
$(document).ready(function() {
|
||||
if (document.getElementById(commentsId)) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// © XlXi 2022
|
||||
// Graphictoria 5
|
||||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
const urlObject = new URL(document.location.href);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,47 +7,6 @@
|
|||
@endsection
|
||||
|
||||
@section('content')
|
||||
{{-- XlXi: MOVE THESE TO JS --}}
|
||||
@if(false)
|
||||
<div class="modal fade show" id="purchase-modal" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content text-center">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Purchase Item</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<p>Would you like to purchase the {{ $asset->typeString() }} "<strong>{{ $asset->name }}</strong>" from {{ $asset->user->username }} for <strong style="color:#e59800!important;font-weight:bold"><img src="{{ asset('images/symbols/token.svg') }}" height="16" width="16" class="img-fluid" style="margin-top:-1px" />{{ number_format($asset->priceInTokens) }}</strong>?</p>
|
||||
<img src={{ asset('images/testing/hat.png') }} width="240" height="240" alt="{{ $asset->name }}" class="mx-auto my-2 img-fluid" />
|
||||
</div>
|
||||
<div class="modal-footer flex-column">
|
||||
<div class="mx-auto">
|
||||
<button class="btn btn-success" data-bs-dismiss="modal">Purchase</button>
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
<p class="text-muted pt-1">You will have <strong style="color:#e59800!important;font-weight:bold"><img src="{{ asset('images/symbols/token.svg') }}" height="16" width="16" class="img-fluid" style="margin-top:-1px" />{{ max(0, number_format(Auth::user()->tokens - $asset->priceInTokens)) }}</strong> after this purchase.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade show" id="purchase-modal" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content text-center">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Insufficient Funds</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>You don't have enough tokens to buy this item.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Ok</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="container mx-auto py-5">
|
||||
@if(!$asset->approved)
|
||||
<div class="alert alert-danger text-center"><strong>This asset is pending approval.</strong> It will not appear in-game and cannot be voted on or purchased at this time.</div>
|
||||
|
|
@ -119,7 +78,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="gt-comments"></div>
|
||||
<div id="gt-comments"
|
||||
data-can-comment="{{ intval(Auth::check()) }}"
|
||||
data-asset-id="{{ $asset->id }}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -11,7 +11,14 @@ Route::middleware('auth')->group(function () {
|
|||
});
|
||||
});
|
||||
|
||||
Route::group(['as' => 'catalog.', 'prefix' => 'catalog'], function() {
|
||||
Route::group(['as' => 'comments.', 'prefix' => 'comments'], function() {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/list-json', 'CommentsController@listJson')->name('list');
|
||||
Route::post('/share', 'CommentsController@share')->name('share')->middleware(['auth', 'throttle:3,2']);
|
||||
});
|
||||
});
|
||||
|
||||
Route::group(['as' => 'shop.', 'prefix' => 'shop'], function() {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/list-json', 'ShopController@listJson')->name('list');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
/*
|
||||
Graphictoria 5 (https://gtoria.net)
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
const mix = require('laravel-mix');
|
||||
require('laravel-mix-banner');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mix Asset Management
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Mix provides a clean, fluent API for defining some Webpack build steps
|
||||
| for your Laravel application. By default, we are compiling the Sass
|
||||
| file for the application as well as bundling up all the JS files.
|
||||
|
|
||||
*/
|
||||
|
||||
mix.js('resources/js/app.js', 'public/js')
|
||||
.js('resources/js/pages/Maintenance.js', 'public/js')
|
||||
.js('resources/js/pages/Dashboard.js', 'public/js')
|
||||
|
|
|
|||
Loading…
Reference in New Issue