Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
Docker VM | 9876743bcf | ||
Docker VM | 0cbd691cc7 |
|
@ -119,3 +119,7 @@ ## Contributing
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the [MIT License](LICENSE).
|
This project is licensed under the [MIT License](LICENSE).
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
Explain setting up oauth
|
||||||
|
https://twitchapps.com/tmi/
|
|
@ -1,32 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Helpers;
|
|
||||||
|
|
||||||
class TwitchHelper
|
|
||||||
{
|
|
||||||
public static function parseMessage($message)
|
|
||||||
{
|
|
||||||
$commandPattern = '/^!place\s([A-P])\s(\d{1,2})\s(\w+)$/i';
|
|
||||||
$shortCommandPattern = '/^!p\s([A-P])(\d{1,2})\s(\w+)$/i';
|
|
||||||
|
|
||||||
if (preg_match($commandPattern, $message, $matches)) {
|
|
||||||
return [
|
|
||||||
'command' => 'place',
|
|
||||||
'x' => $matches[1],
|
|
||||||
'y' => $matches[2],
|
|
||||||
'color' => $matches[3]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preg_match($shortCommandPattern, $message, $matches)) {
|
|
||||||
return [
|
|
||||||
'command' => 'place',
|
|
||||||
'x' => $matches[1],
|
|
||||||
'y' => $matches[2],
|
|
||||||
'color' => $matches[3]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class TwitchHelper
|
||||||
|
{
|
||||||
|
public static function parseMessage($message)
|
||||||
|
{
|
||||||
|
Log::debug('TwitchHelper::parseMessage', ['message' => $message]);
|
||||||
|
|
||||||
|
// Match commands like !place A 1 red, !p A 1 red, !paint A 1 red
|
||||||
|
// Updated regex to handle hyphenated color names
|
||||||
|
$commandPattern = '/^(?:!place|!p|!paint)\s([A-P])\s(\d{1,2})\s([\w-]+)$/i';
|
||||||
|
|
||||||
|
if (preg_match($commandPattern, $message, $matches)) {
|
||||||
|
Log::debug('TwitchHelper::parseMessage - Command matched', ['matches' => $matches]);
|
||||||
|
return [
|
||||||
|
'command' => 'place',
|
||||||
|
'x' => $matches[1],
|
||||||
|
'y' => $matches[2],
|
||||||
|
'color' => $matches[3]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug('TwitchHelper::parseMessage - No match found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\GameSession;
|
||||||
|
use App\Models\Palette;
|
||||||
|
use App\Models\CurrentState;
|
||||||
|
|
||||||
|
class AdministrationController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$palettes = Palette::all();
|
||||||
|
$currentGame = GameSession::whereNull('ended_at')->with('palette')->latest()->first();
|
||||||
|
$currentState = $currentGame ? CurrentState::where('game_session_id', $currentGame->id)->with('color')->get() : [];
|
||||||
|
|
||||||
|
return view('administration.index', compact('palettes', 'currentGame', 'currentState'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changePalette(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'palette_id' => 'required|exists:palettes,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$currentGame = GameSession::whereNull('ended_at')->latest()->first();
|
||||||
|
if ($currentGame) {
|
||||||
|
$currentGame->palette_id = $request->palette_id;
|
||||||
|
$currentGame->save();
|
||||||
|
return redirect()->route('administration.index')->with('success', 'Palette changed successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('administration.index')->with('error', 'No active game session.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createGame(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'session_name' => 'required|string|max:255',
|
||||||
|
'palette_id' => 'required|exists:palettes,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// End the current game session if one is active
|
||||||
|
$currentGame = GameSession::whereNull('ended_at')->latest()->first();
|
||||||
|
if ($currentGame) {
|
||||||
|
$currentGame->update(['ended_at' => now()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new game session
|
||||||
|
$newGame = GameSession::create([
|
||||||
|
'session_name' => $request->session_name,
|
||||||
|
'palette_id' => $request->palette_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reset the board for the new game session
|
||||||
|
CurrentState::where('game_session_id', $newGame->id)->delete();
|
||||||
|
|
||||||
|
return redirect()->route('administration.index')->with('success', 'New game session created and board reset.');
|
||||||
|
}
|
||||||
|
}
|
0
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
Normal file → Executable file
0
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
Normal file → Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\CommandHistory;
|
||||||
|
use App\Models\CurrentState;
|
||||||
|
use App\Models\GameSession;
|
||||||
|
use App\Models\PaletteColor;
|
||||||
|
use App\Models\TwitchUser;
|
||||||
|
|
||||||
|
class CommandController extends Controller
|
||||||
|
{
|
||||||
|
public function parseChatMessage(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'username' => 'required|string',
|
||||||
|
'x' => 'required|string',
|
||||||
|
'y' => 'required|string',
|
||||||
|
'color' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = TwitchUser::firstOrCreate(['username' => $request->username]);
|
||||||
|
$currentGame = GameSession::whereNull('ended_at')->latest()->first();
|
||||||
|
if (!$currentGame) {
|
||||||
|
return response()->json(['error' => 'No active game session'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$color = PaletteColor::where('hex_value', $request->color)->first();
|
||||||
|
if (!$color) {
|
||||||
|
return response()->json(['error' => 'Invalid color'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = CommandHistory::create([
|
||||||
|
'twitch_user_id' => $user->id,
|
||||||
|
'game_session_id' => $currentGame->id,
|
||||||
|
'x' => $request->x,
|
||||||
|
'y' => $request->y,
|
||||||
|
'palette_color_id' => $color->id,
|
||||||
|
'timestamp' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
CurrentState::updateOrCreate(
|
||||||
|
['game_session_id' => $currentGame->id, 'x' => $request->x, 'y' => $request->y],
|
||||||
|
['palette_color_id' => $color->id, 'updated_by' => $user->id, 'timestamp' => now()]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Logic to check if the current state matches the winning state
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return response()->json($command);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\CurrentState;
|
||||||
|
use App\Models\GameSession;
|
||||||
|
|
||||||
|
class CurrentStateController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$currentGame = GameSession::whereNull('ended_at')->latest()->first();
|
||||||
|
if (!$currentGame) {
|
||||||
|
return response()->json(['error' => 'No active game session'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentState = CurrentState::where('game_session_id', $currentGame->id)->with('color')->get();
|
||||||
|
return response()->json($currentState);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\GameSession;
|
||||||
|
use App\Models\Palette;
|
||||||
|
use App\Models\WinningState;
|
||||||
|
|
||||||
|
class GameSessionController extends Controller
|
||||||
|
{
|
||||||
|
public function start(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'session_name' => 'required|string|max:255',
|
||||||
|
'palette_id' => 'required|exists:palettes,id',
|
||||||
|
'image_path' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$gameSession = GameSession::create([
|
||||||
|
'session_name' => $request->session_name,
|
||||||
|
'palette_id' => $request->palette_id,
|
||||||
|
'image_path' => $request->image_path,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Logic to initialize the winning state based on the provided image
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return response()->json($gameSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function end(Request $request, $id)
|
||||||
|
{
|
||||||
|
$gameSession = GameSession::findOrFail($id);
|
||||||
|
$gameSession->update(['ended_at' => now()]);
|
||||||
|
|
||||||
|
return response()->json($gameSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current()
|
||||||
|
{
|
||||||
|
$currentGame = GameSession::whereNull('ended_at')->latest()->first();
|
||||||
|
return response()->json($currentGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentGameSession()
|
||||||
|
{
|
||||||
|
$gameSession = GameSession::whereNull('ended_at')->latest()->first();
|
||||||
|
|
||||||
|
if (!$gameSession) {
|
||||||
|
$gameSession = GameSession::create(['session_name' => 'Session ' . now(), 'palette_id' => 1, 'image_path' => '']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $gameSession;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\Palette;
|
||||||
|
use App\Models\PaletteColor;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class PaletteController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$palettes = Palette::all();
|
||||||
|
|
||||||
|
// Check if it's an AJAX request
|
||||||
|
if ($request->ajax()) {
|
||||||
|
return response()->json($palettes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('palette-editor', compact('palettes'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|unique:palettes,name',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$palette = Palette::create(['name' => $request->name]);
|
||||||
|
return response()->json($palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addColor(Request $request, Palette $palette)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|regex:/^[a-z-]+$/',
|
||||||
|
'hex_value' => 'required|regex:/^#[a-fA-F0-9]{6}$/'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$color = PaletteColor::create([
|
||||||
|
'palette_id' => $palette->id,
|
||||||
|
'name' => $request->name,
|
||||||
|
'hex_value' => $request->hex_value
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateColor(Request $request, Palette $palette, PaletteColor $color)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|regex:/^[a-z-]+$/',
|
||||||
|
'hex_value' => 'required|regex:/^#[a-fA-F0-9]{6}$/'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$color->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'hex_value' => $request->hex_value
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json($color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteColor(Palette $palette, PaletteColor $color)
|
||||||
|
{
|
||||||
|
if ($palette->name === 'default-colors') {
|
||||||
|
return response()->json(['message' => 'Cannot delete colors from the default palette'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$color->delete();
|
||||||
|
return response()->json(['message' => 'Color deleted']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColors(Palette $palette)
|
||||||
|
{
|
||||||
|
return response()->json($palette->colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function export(Palette $palette)
|
||||||
|
{
|
||||||
|
$palette->load('colors'); // Load the colors relationship
|
||||||
|
$fileName = $palette->name . '.pal';
|
||||||
|
$content = $palette->colors->map(function($color) {
|
||||||
|
return [
|
||||||
|
'name' => $color->name,
|
||||||
|
'hex_value' => $color->hex_value
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
$jsonContent = json_encode($content, JSON_PRETTY_PRINT); // Format JSON for readability
|
||||||
|
|
||||||
|
return response($jsonContent, 200)
|
||||||
|
->header('Content-Type', 'application/json')
|
||||||
|
->header('Content-Disposition', 'attachment; filename="'.$fileName.'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function import(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'file' => 'required|file|mimes:json,txt'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$file = $request->file('file');
|
||||||
|
$content = json_decode(file_get_contents($file), true);
|
||||||
|
|
||||||
|
$paletteName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
|
||||||
|
$palette = Palette::firstOrCreate(['name' => $paletteName]);
|
||||||
|
|
||||||
|
foreach ($content as $color) {
|
||||||
|
PaletteColor::updateOrCreate(
|
||||||
|
['palette_id' => $palette->id, 'name' => $color['name']],
|
||||||
|
['hex_value' => $color['hex_value']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Palette imported successfully', 'palette' => $palette]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rename(Request $request, Palette $palette)
|
||||||
|
{
|
||||||
|
if ($palette->name === 'default-colors') {
|
||||||
|
return response()->json(['message' => 'Cannot rename the default palette'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|unique:palettes,name',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$palette->name = $request->name;
|
||||||
|
$palette->save();
|
||||||
|
|
||||||
|
return response()->json($palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function delete(Palette $palette)
|
||||||
|
{
|
||||||
|
if ($palette->name === 'default-colors') {
|
||||||
|
return response()->json(['message' => 'Cannot delete the default palette'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$palette->colors()->delete();
|
||||||
|
$palette->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Palette deleted']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,23 +4,105 @@
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Helpers\TwitchHelper;
|
use App\Helpers\TwitchHelper;
|
||||||
|
use App\Models\TwitchUser;
|
||||||
|
use App\Models\CommandHistory;
|
||||||
|
use App\Models\CurrentState;
|
||||||
|
use App\Models\PaletteColor;
|
||||||
|
use App\Http\Controllers\GameSessionController;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class TwitchController extends Controller
|
class TwitchController extends Controller
|
||||||
{
|
{
|
||||||
public function parseChatMessage(Request $request)
|
public function parseChatMessage(Request $request)
|
||||||
{
|
{
|
||||||
|
Log::info("Entered parseChatMessage");
|
||||||
$message = $request->input('message');
|
$message = $request->input('message');
|
||||||
|
$username = $request->input('username');
|
||||||
|
$gameSession = app(GameSessionController::class)->getCurrentGameSession();
|
||||||
|
|
||||||
if (!$message) {
|
Log::debug('TwitchController::parseChatMessage', [
|
||||||
return response()->json(['error' => 'No message provided'], 400);
|
'message' => $message,
|
||||||
|
'username' => $username,
|
||||||
|
'gameSessionId' => $gameSession->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$message || !$username || !$gameSession) {
|
||||||
|
Log::debug('TwitchController::parseChatMessage - Missing input or invalid game session', [
|
||||||
|
'message' => $message,
|
||||||
|
'username' => $username,
|
||||||
|
'gameSession' => $gameSession
|
||||||
|
]);
|
||||||
|
return response()->json(['error' => 'No message, username, or valid game session provided'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user = TwitchUser::firstOrCreate(['username' => $username]);
|
||||||
|
|
||||||
$parsedCommand = TwitchHelper::parseMessage($message);
|
$parsedCommand = TwitchHelper::parseMessage($message);
|
||||||
|
Log::debug('TwitchController::parseChatMessage - Parsed Command', ['parsedCommand' => $parsedCommand]);
|
||||||
|
|
||||||
if ($parsedCommand) {
|
if ($parsedCommand) {
|
||||||
return response()->json($parsedCommand);
|
$color = $this->getColorFromName($parsedCommand['color'], $gameSession->palette_id);
|
||||||
|
if (!$color) {
|
||||||
|
return response()->json(['error' => 'Invalid color'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommandHistory::create([
|
||||||
|
'twitch_user_id' => $user->id,
|
||||||
|
'command' => $parsedCommand['command'],
|
||||||
|
'x' => $parsedCommand['x'] ?? null,
|
||||||
|
'y' => $parsedCommand['y'] ?? null,
|
||||||
|
'palette_color_id' => $color->id,
|
||||||
|
'game_session_id' => $gameSession->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
switch ($parsedCommand['command']) {
|
||||||
|
case 'place':
|
||||||
|
$this->handlePlaceCommand($parsedCommand, $user->id, $gameSession->id, $color->id);
|
||||||
|
break;
|
||||||
|
// Add cases for other commands like 'row', 'column', 'fill'
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'command' => $parsedCommand,
|
||||||
|
'color' => [
|
||||||
|
'hex_value' => $color->hex_value
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::debug('TwitchController::parseChatMessage - Invalid command', ['message' => $message]);
|
||||||
return response()->json(['error' => 'Invalid command'], 400);
|
return response()->json(['error' => 'Invalid command'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function handlePlaceCommand($parsedCommand, $userId, $gameSessionId, $colorId)
|
||||||
|
{
|
||||||
|
Log::debug('TwitchController::handlePlaceCommand', ['command' => $parsedCommand]);
|
||||||
|
|
||||||
|
$x = $parsedCommand['x'];
|
||||||
|
$y = $parsedCommand['y'];
|
||||||
|
|
||||||
|
$currentState = CurrentState::where('x', $x)
|
||||||
|
->where('y', $y)
|
||||||
|
->where('game_session_id', $gameSessionId)
|
||||||
|
->first();
|
||||||
|
Log::debug('TwitchController::handlePlaceCommand - Retrieved Current State', ['currentState' => $currentState]);
|
||||||
|
|
||||||
|
if ($currentState) {
|
||||||
|
$currentState->update(['palette_color_id' => $colorId, 'updated_by' => $userId, 'updated_at' => now()]);
|
||||||
|
} else {
|
||||||
|
CurrentState::create([
|
||||||
|
'x' => $x,
|
||||||
|
'y' => $y,
|
||||||
|
'palette_color_id' => $colorId,
|
||||||
|
'updated_by' => $userId,
|
||||||
|
'updated_at' => now(),
|
||||||
|
'game_session_id' => $gameSessionId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getColorFromName($colorName, $paletteId)
|
||||||
|
{
|
||||||
|
return PaletteColor::where('palette_id', $paletteId)->where('name', $colorName)->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CommandHistory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'command_history'; // Specify the correct table name
|
||||||
|
|
||||||
|
protected $fillable = ['twitch_user_id', 'game_session_id', 'x', 'y', 'palette_color_id', 'timestamp'];
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(TwitchUser::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gameSession()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(GameSession::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function color()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PaletteColor::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CurrentState extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'current_state';
|
||||||
|
|
||||||
|
protected $fillable = ['game_session_id', 'x', 'y', 'palette_color_id', 'updated_by', 'updated_at'];
|
||||||
|
|
||||||
|
public function gameSession()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(GameSession::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function color()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PaletteColor::class, 'palette_color_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedBy()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(TwitchUser::class, 'updated_by');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class GameSession extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['session_name', 'palette_id', 'image_path', 'ended_at'];
|
||||||
|
|
||||||
|
public function palette()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Palette::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commands()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CommandHistory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function currentState()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CurrentState::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function winningState()
|
||||||
|
{
|
||||||
|
return $this->hasMany(WinningState::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Palette extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['name'];
|
||||||
|
|
||||||
|
public function colors()
|
||||||
|
{
|
||||||
|
return $this->hasMany(PaletteColor::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PaletteColor extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['palette_id', 'name', 'hex_value'];
|
||||||
|
|
||||||
|
public function palette()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Palette::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class SourceImage extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['name', 'pixel_state', 'palette'];
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class TwitchUser extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['username'];
|
||||||
|
|
||||||
|
public function commands()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CommandHistory::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class WinningState extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['game_session_id', 'x', 'y', 'palette_color_id'];
|
||||||
|
|
||||||
|
public function gameSession()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(GameSession::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function color()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PaletteColor::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateTwitchUsersTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('twitch_users', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('username')->unique();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('twitch_users');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreatePalettesTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('palettes', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('palettes');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreatePaletteColorsTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('palette_colors', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('palette_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('hex_value');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert default palette
|
||||||
|
DB::table('palettes')->insert([
|
||||||
|
'name' => 'default-colors',
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$defaultColors = [
|
||||||
|
["white", "#FFFFFF"],
|
||||||
|
["light-gray", "#E4E4E4"],
|
||||||
|
["medium-gray", "#888888"],
|
||||||
|
["dark-gray", "#222222"],
|
||||||
|
["pink", "#FFA7D1"],
|
||||||
|
["red", "#E50000"],
|
||||||
|
["orange", "#E59500"],
|
||||||
|
["brown", "#A06A42"],
|
||||||
|
["yellow", "#E5D900"],
|
||||||
|
["light-green", "#94E044"],
|
||||||
|
["green", "#02BE01"],
|
||||||
|
["cyan", "#00D3DD"],
|
||||||
|
["blue", "#0083C7"],
|
||||||
|
["dark-blue", "#0000EA"],
|
||||||
|
["purple", "#CF6EE4"],
|
||||||
|
["dark-purple", "#820080"],
|
||||||
|
["black", "#000000"]
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defaultColors as $color) {
|
||||||
|
DB::table('palette_colors')->insert([
|
||||||
|
'palette_id' => 1,
|
||||||
|
'name' => $color[0],
|
||||||
|
'hex_value' => $color[1],
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('palette_colors');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateGameSessionsTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('game_sessions', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('session_name');
|
||||||
|
$table->foreignId('palette_id')->constrained('palettes')->onDelete('cascade')->default(1);
|
||||||
|
$table->string('image_path')->nullable();
|
||||||
|
$table->timestamp('ended_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('game_sessions');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateCommandHistoryTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('command_history', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('twitch_user_id')->constrained('twitch_users')->onDelete('cascade');
|
||||||
|
$table->foreignId('game_session_id')->constrained('game_sessions')->onDelete('cascade');
|
||||||
|
$table->string('x');
|
||||||
|
$table->string('y');
|
||||||
|
$table->foreignId('palette_color_id')->constrained('palette_colors')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('command_history');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateCurrentStateTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('current_state', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('game_session_id')->constrained('game_sessions')->onDelete('cascade');
|
||||||
|
$table->string('x');
|
||||||
|
$table->string('y');
|
||||||
|
$table->foreignId('palette_color_id')->constrained('palette_colors')->onDelete('cascade');
|
||||||
|
$table->foreignId('updated_by')->constrained('twitch_users')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('current_state');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateWinningStateTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('winning_state', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('game_session_id')->constrained('game_sessions')->onDelete('cascade');
|
||||||
|
$table->string('x');
|
||||||
|
$table->string('y');
|
||||||
|
$table->foreignId('palette_color_id')->constrained('palette_colors')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('winning_state');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--grid-size: 16; /* Adjust this value for grid size (e.g., 3 for 3x3) */
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vh; /* Keep the grid square */
|
||||||
|
max-width: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(calc(var(--grid-size) + 2), 1fr);
|
||||||
|
grid-template-rows: repeat(calc(var(--grid-size) + 2), 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-size: calc(0.5vw + 0.5vh); /* Adjust font size for better visibility */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
.pickr {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pickr .pcr-button {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rename-palette-name {
|
||||||
|
border-radius: 25px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
|
@ -30,34 +30,26 @@ ws.onmessage = (event) => {
|
||||||
if (parsedMessage && parsedMessage.command === 'PRIVMSG') {
|
if (parsedMessage && parsedMessage.command === 'PRIVMSG') {
|
||||||
const chatMessage = parsedMessage.params.slice(1).join(' ').trim();
|
const chatMessage = parsedMessage.params.slice(1).join(' ').trim();
|
||||||
const username = parsedMessage.tags['display-name'] || parsedMessage.prefix.split('!')[0];
|
const username = parsedMessage.tags['display-name'] || parsedMessage.prefix.split('!')[0];
|
||||||
|
const gameSessionId = 1; // Replace with logic to get the current game session ID
|
||||||
|
|
||||||
console.log('Chat Message:', chatMessage);
|
console.log('Chat Message:', chatMessage);
|
||||||
console.log('Username:', username);
|
console.log('Username:', username);
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('chat-message', { detail: { user: username, message: chatMessage } }));
|
window.dispatchEvent(new CustomEvent('chat-message', { detail: { user: username, message: chatMessage } }));
|
||||||
|
|
||||||
// Process !place command
|
|
||||||
fetch('/twitch/parse', {
|
fetch('/twitch/parse', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ message: chatMessage })
|
body: JSON.stringify({ message: chatMessage, username: username, game_session_id: gameSessionId })
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data && data.command === 'place') {
|
if (data && data.command === 'place') {
|
||||||
console.log('Parsed Command:', data);
|
console.log('Parsed Place Command:', data);
|
||||||
// Call the existing API with parsed coordinates and color
|
colorCell(`${data.x}${data.y}`, data.color);
|
||||||
// Example API call: /api/place/{x}/{y}/{color}
|
|
||||||
fetch(`/api/place/${data.x}/${data.y}/${data.color}`, { method: 'POST' })
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
console.log('API Result:', result);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('API Error:', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
@ -66,6 +58,7 @@ ws.onmessage = (event) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
ws.onerror = (error) => {
|
||||||
console.error('WebSocket Error:', error);
|
console.error('WebSocket Error:', error);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
function colorCell(cellName, hexValue) {
|
||||||
|
console.log(`colorCell called with: ${cellName}, ${hexValue}`);
|
||||||
|
|
||||||
|
// Find the cell using its class name
|
||||||
|
const cell = document.querySelector(`.cell.${cellName}`);
|
||||||
|
|
||||||
|
// If the cell exists, change its background color to the provided hex value
|
||||||
|
if (cell) {
|
||||||
|
cell.style.backgroundColor = hexValue;
|
||||||
|
} else {
|
||||||
|
console.warn(`Cell ${cellName} not found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach colorCell function to the window object to make it globally accessible
|
||||||
|
window.colorCell = colorCell;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const gridSize = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--grid-size').trim());
|
||||||
|
const grid = document.querySelector('.grid-container');
|
||||||
|
|
||||||
|
function getLabel(index) {
|
||||||
|
let label = '';
|
||||||
|
while (index >= 0) {
|
||||||
|
label = String.fromCharCode((index % 26) + 65) + label;
|
||||||
|
index = Math.floor(index / 26) - 1;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create top labels
|
||||||
|
grid.innerHTML += '<div class="label"></div>'; // Empty top-left corner
|
||||||
|
for (let i = 0; i < gridSize; i++) {
|
||||||
|
grid.innerHTML += `<div class="label">${getLabel(i)}</div>`;
|
||||||
|
}
|
||||||
|
grid.innerHTML += '<div class="label"></div>'; // Empty top-right corner
|
||||||
|
|
||||||
|
// Create rows with side labels and cells
|
||||||
|
for (let i = 1; i <= gridSize; i++) {
|
||||||
|
grid.innerHTML += `<div class="label">${i}</div>`;
|
||||||
|
for (let j = 0; j < gridSize; j++) {
|
||||||
|
let cellName = `${getLabel(j)}${i}`; // This will generate names like A1, A2, B1, etc.
|
||||||
|
grid.innerHTML += `<div class="cell ${cellName}"></div>`;
|
||||||
|
}
|
||||||
|
grid.innerHTML += `<div class="label">${i}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create bottom labels
|
||||||
|
grid.innerHTML += '<div class="label"></div>'; // Empty bottom-left corner
|
||||||
|
for (let i = 0; i < gridSize; i++) {
|
||||||
|
grid.innerHTML += `<div class="label">${getLabel(i)}</div>`;
|
||||||
|
}
|
||||||
|
grid.innerHTML += '<div class="label"></div>'; // Empty bottom-right corner
|
||||||
|
|
||||||
|
// Fetch and apply the current state
|
||||||
|
fetch('/api/current-state')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
data.forEach(pixel => {
|
||||||
|
if (pixel.color && pixel.color.hex_value) {
|
||||||
|
colorCell(`${pixel.x}${pixel.y}`, pixel.color.hex_value);
|
||||||
|
} else {
|
||||||
|
console.warn(`No color found for pixel at ${pixel.x}${pixel.y}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching current state:', error);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,455 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('DOM fully loaded and parsed.');
|
||||||
|
|
||||||
|
const paletteSelector = document.getElementById('palette-selector');
|
||||||
|
const newPaletteButton = document.getElementById('new-palette');
|
||||||
|
const newPaletteForm = document.getElementById('new-palette-form');
|
||||||
|
const createPaletteForm = document.getElementById('create-palette-form');
|
||||||
|
const addColorForm = document.getElementById('add-color-form');
|
||||||
|
const createColorForm = document.getElementById('create-color-form');
|
||||||
|
const colorEditorsDiv = document.getElementById('color-editors');
|
||||||
|
const addColorButton = document.getElementById('add-color');
|
||||||
|
const savePaletteButton = document.getElementById('save-palette');
|
||||||
|
const exportPaletteButton = document.getElementById('export-palette');
|
||||||
|
const importPaletteButton = document.getElementById('import-palette');
|
||||||
|
const importFileInput = document.getElementById('import-file');
|
||||||
|
const renamePaletteButton = document.getElementById('rename-palette');
|
||||||
|
const renamePaletteNameInput = document.getElementById('rename-palette-name');
|
||||||
|
const deletePaletteButton = document.getElementById('delete-palette');
|
||||||
|
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'), {});
|
||||||
|
const confirmDeleteCheckbox = document.getElementById('confirmDeleteCheckbox');
|
||||||
|
const confirmDeleteButton = document.getElementById('confirmDeleteButton');
|
||||||
|
|
||||||
|
// CSRF token
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||||
|
|
||||||
|
// Logging element states
|
||||||
|
console.log('paletteSelector:', paletteSelector);
|
||||||
|
console.log('newPaletteButton:', newPaletteButton);
|
||||||
|
console.log('newPaletteForm:', newPaletteForm);
|
||||||
|
console.log('createPaletteForm:', createPaletteForm);
|
||||||
|
console.log('addColorForm:', addColorForm);
|
||||||
|
console.log('createColorForm:', createColorForm);
|
||||||
|
console.log('colorEditorsDiv:', colorEditorsDiv);
|
||||||
|
console.log('addColorButton:', addColorButton);
|
||||||
|
console.log('savePaletteButton:', savePaletteButton);
|
||||||
|
console.log('deletePaletteButton:', deletePaletteButton);
|
||||||
|
console.log('csrfToken:', csrfToken);
|
||||||
|
|
||||||
|
if (!paletteSelector || !newPaletteButton || !newPaletteForm || !createPaletteForm || !addColorForm || !createColorForm || !colorEditorsDiv || !addColorButton || !savePaletteButton || !exportPaletteButton || !importPaletteButton || !importFileInput || !renamePaletteButton || !renamePaletteNameInput || !deletePaletteButton) {
|
||||||
|
console.error('Missing required elements on the page.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPaletteButton.addEventListener('click', function() {
|
||||||
|
newPaletteForm.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
createPaletteForm.addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = new FormData(createPaletteForm);
|
||||||
|
console.log('Creating new palette with data:', formData);
|
||||||
|
fetch('/palette-editor', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Palette created:', data);
|
||||||
|
fetchPalettes().then(() => {
|
||||||
|
const newOption = document.createElement('option');
|
||||||
|
newOption.value = data.id;
|
||||||
|
newOption.text = data.name;
|
||||||
|
paletteSelector.add(newOption);
|
||||||
|
paletteSelector.value = data.id;
|
||||||
|
loadPaletteColors(data.id);
|
||||||
|
});
|
||||||
|
newPaletteForm.style.display = 'none';
|
||||||
|
createPaletteForm.reset();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error creating palette:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
paletteSelector.addEventListener('change', function() {
|
||||||
|
const paletteId = paletteSelector.value;
|
||||||
|
console.log('Palette selected:', paletteId);
|
||||||
|
if (paletteId) {
|
||||||
|
loadPaletteColors(paletteId);
|
||||||
|
checkPaletteRestrictions();
|
||||||
|
} else {
|
||||||
|
addColorForm.style.display = 'none';
|
||||||
|
colorEditorsDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addColorButton.addEventListener('click', function() {
|
||||||
|
addColorEditor();
|
||||||
|
});
|
||||||
|
|
||||||
|
savePaletteButton.addEventListener('click', function() {
|
||||||
|
const paletteId = createColorForm.getAttribute('data-palette-id');
|
||||||
|
const colorEditors = document.querySelectorAll('.color-editor');
|
||||||
|
const colorUpdates = [];
|
||||||
|
|
||||||
|
colorEditors.forEach(editor => {
|
||||||
|
const colorId = editor.getAttribute('data-color-id');
|
||||||
|
const colorName = editor.querySelector('.color-name').value;
|
||||||
|
const colorHex = editor.querySelector('.color-hex-input').value;
|
||||||
|
colorUpdates.push({
|
||||||
|
colorId,
|
||||||
|
colorName,
|
||||||
|
colorHex
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Saving palette with ID:', paletteId);
|
||||||
|
colorUpdates.forEach(({colorId, colorName, colorHex}) => {
|
||||||
|
console.log(`Saving color: ${colorName} (${colorHex}) with ID: ${colorId}`);
|
||||||
|
if (colorId) {
|
||||||
|
// Update existing color
|
||||||
|
fetch(`/palette-editor/${paletteId}/colors/${colorId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name: colorName, hex_value: colorHex })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Color updated:', data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating color:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Add new color
|
||||||
|
fetch(`/palette-editor/${paletteId}/colors`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name: colorName, hex_value: colorHex })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Color added:', data);
|
||||||
|
editor.setAttribute('data-color-id', data.id);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error adding color:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
exportPaletteButton.addEventListener('click', function() {
|
||||||
|
const paletteId = paletteSelector.value;
|
||||||
|
console.log('Exporting palette with ID:', paletteId);
|
||||||
|
if (paletteId) {
|
||||||
|
fetch(`/palette-editor/${paletteId}/export`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.blob();
|
||||||
|
})
|
||||||
|
.then(blob => {
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${paletteSelector.options[paletteSelector.selectedIndex].text}.pal`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error exporting palette:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Please select a palette to export.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
importPaletteButton.addEventListener('click', function() {
|
||||||
|
importFileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
importFileInput.addEventListener('change', function(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
console.log('Importing palette from file:', file.name);
|
||||||
|
fetch('/palette-editor/import', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Palette imported successfully', data);
|
||||||
|
const newOption = document.createElement('option');
|
||||||
|
newOption.value = data.palette.id;
|
||||||
|
newOption.text = data.palette.name;
|
||||||
|
paletteSelector.add(newOption);
|
||||||
|
paletteSelector.value = data.palette.id;
|
||||||
|
loadPaletteColors(data.palette.id);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error importing palette:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
renamePaletteButton.addEventListener('click', function() {
|
||||||
|
const paletteId = paletteSelector.value;
|
||||||
|
const newName = renamePaletteNameInput.value.trim();
|
||||||
|
console.log('Renaming palette ID:', paletteId, 'to:', newName);
|
||||||
|
if (!paletteId) {
|
||||||
|
alert('Please select a palette to rename.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!newName) {
|
||||||
|
alert('Please enter a new name for the palette.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/palette-editor/${paletteId}/rename`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name: newName })
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
console.log('Palette renamed:', data);
|
||||||
|
fetchPalettes().then(() => {
|
||||||
|
const option = Array.from(paletteSelector.options).find(option => option.value == paletteId);
|
||||||
|
if (option) {
|
||||||
|
option.text = newName;
|
||||||
|
paletteSelector.value = paletteId;
|
||||||
|
}
|
||||||
|
loadPaletteColors(paletteId);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error renaming palette:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
deletePaletteButton.addEventListener('click', function() {
|
||||||
|
const paletteId = paletteSelector.value;
|
||||||
|
console.log('Preparing to delete palette ID:', paletteId);
|
||||||
|
if (!paletteId) {
|
||||||
|
alert('Please select a palette to delete.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Show modal
|
||||||
|
deleteModal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmDeleteCheckbox.addEventListener('change', function() {
|
||||||
|
confirmDeleteButton.disabled = !confirmDeleteCheckbox.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmDeleteButton.addEventListener('click', function() {
|
||||||
|
const paletteId = paletteSelector.value;
|
||||||
|
console.log('Deleting palette with ID:', paletteId);
|
||||||
|
if (paletteId && confirmDeleteCheckbox.checked) {
|
||||||
|
fetch(`/palette-editor/${paletteId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Palette deleted:', data);
|
||||||
|
fetchPalettes().then(() => {
|
||||||
|
const option = Array.from(paletteSelector.options).find(option => option.value == paletteId);
|
||||||
|
if (option) {
|
||||||
|
paletteSelector.removeChild(option);
|
||||||
|
}
|
||||||
|
const defaultPaletteOption = Array.from(paletteSelector.options).find(option => option.text === 'default-colors');
|
||||||
|
if (defaultPaletteOption) {
|
||||||
|
defaultPaletteOption.selected = true;
|
||||||
|
loadPaletteColors(defaultPaletteOption.value);
|
||||||
|
} else {
|
||||||
|
paletteSelector.value = '';
|
||||||
|
colorEditorsDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deleteModal.hide();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error deleting palette:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadPaletteColors(paletteId) {
|
||||||
|
console.log('Loading colors for palette ID:', paletteId);
|
||||||
|
fetch(`/palette-editor/${paletteId}/colors`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Loaded palette colors:', data);
|
||||||
|
colorEditorsDiv.innerHTML = '';
|
||||||
|
data.forEach(color => {
|
||||||
|
addColorEditor(color.id, color.name, color.hex_value);
|
||||||
|
});
|
||||||
|
addColorForm.style.display = 'block';
|
||||||
|
createColorForm.setAttribute('data-palette-id', paletteId);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading palette colors:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addColorEditor(colorId = '', colorName = '', colorHex = '#ffffff') {
|
||||||
|
console.log(`Adding color editor for color: ${colorName} (${colorHex}) with ID: ${colorId}`);
|
||||||
|
const editor = document.createElement('div');
|
||||||
|
editor.className = 'color-editor col-md-2 mb-3';
|
||||||
|
editor.setAttribute('data-color-id', colorId);
|
||||||
|
editor.innerHTML = `
|
||||||
|
<div class="form-row align-items-end">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Color Name:</label>
|
||||||
|
<input type="text" class="form-control color-name" value="${colorName}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Color Hex:</label>
|
||||||
|
<div class="color-picker"></div>
|
||||||
|
<input type="hidden" class="color-hex-input" value="${colorHex}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 text-right mt-2">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm delete-color"><i class="fas fa-trash-alt"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
colorEditorsDiv.appendChild(editor);
|
||||||
|
|
||||||
|
const deleteButton = editor.querySelector('.delete-color');
|
||||||
|
deleteButton.addEventListener('click', function() {
|
||||||
|
const paletteId = createColorForm.getAttribute('data-palette-id');
|
||||||
|
if (paletteId === '1') {
|
||||||
|
alert('Cannot delete colors from the default-colors palette.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Deleting color ID:', colorId, 'from palette ID:', paletteId);
|
||||||
|
if (colorId) {
|
||||||
|
fetch(`/palette-editor/${paletteId}/colors/${colorId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Color deleted:', data.message);
|
||||||
|
colorEditorsDiv.removeChild(editor);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error deleting color:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
colorEditorsDiv.removeChild(editor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const pickr = Pickr.create({
|
||||||
|
el: editor.querySelector('.color-picker'),
|
||||||
|
theme: 'classic', // or 'monolith', or 'nano'
|
||||||
|
default: colorHex,
|
||||||
|
components: {
|
||||||
|
preview: true,
|
||||||
|
opacity: false,
|
||||||
|
hue: true,
|
||||||
|
|
||||||
|
interaction: {
|
||||||
|
hex: true,
|
||||||
|
input: true,
|
||||||
|
clear: true,
|
||||||
|
save: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pickr.on('save', (color, instance) => {
|
||||||
|
editor.querySelector('.color-hex-input').value = color.toHEXA().toString();
|
||||||
|
pickr.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
pickr.on('clear', instance => {
|
||||||
|
editor.querySelector('.color-hex-input').value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchPalettes() {
|
||||||
|
console.log('Fetching palettes from the server');
|
||||||
|
return fetch('/palette-editor')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Fetched palettes:', data);
|
||||||
|
paletteSelector.innerHTML = '';
|
||||||
|
data.forEach(palette => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = palette.id;
|
||||||
|
option.text = palette.name;
|
||||||
|
paletteSelector.add(option);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching palettes:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkPaletteRestrictions() {
|
||||||
|
const paletteId = paletteSelector.value;
|
||||||
|
const isDefaultPalette = paletteId === '1';
|
||||||
|
|
||||||
|
console.log('Checking restrictions for palette ID:', paletteId, 'isDefaultPalette:', isDefaultPalette);
|
||||||
|
|
||||||
|
deletePaletteButton.disabled = isDefaultPalette;
|
||||||
|
addColorButton.disabled = isDefaultPalette;
|
||||||
|
savePaletteButton.disabled = isDefaultPalette;
|
||||||
|
|
||||||
|
if (isDefaultPalette) {
|
||||||
|
document.querySelectorAll('.delete-color').forEach(button => {
|
||||||
|
button.disabled = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.delete-color').forEach(button => {
|
||||||
|
button.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load default colors on page load
|
||||||
|
window.onload = function() {
|
||||||
|
console.log('Page loaded, selecting default palette if available');
|
||||||
|
const defaultPaletteOption = Array.from(paletteSelector.options).find(option => option.text === 'default-colors');
|
||||||
|
if (defaultPaletteOption) {
|
||||||
|
defaultPaletteOption.selected = true;
|
||||||
|
loadPaletteColors(defaultPaletteOption.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,131 @@
|
||||||
|
let ws;
|
||||||
|
|
||||||
|
function connectWebSocket() {
|
||||||
|
console.log("Attempting to connect to Twitch chat with websocket...");
|
||||||
|
ws = new WebSocket('wss://irc-ws.chat.twitch.tv:443');
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
console.log('Connected to Twitch chat');
|
||||||
|
|
||||||
|
const oauthToken = import.meta.env.VITE_TWITCH_BOT_OAUTH_TOKEN;
|
||||||
|
const username = import.meta.env.VITE_TWITCH_BOT_USERNAME;
|
||||||
|
const channel = import.meta.env.VITE_TWITCH_CHANNEL;
|
||||||
|
|
||||||
|
console.log('OAuth Token:', oauthToken ? 'Loaded' : 'Missing');
|
||||||
|
console.log('Username:', username ? username : 'Missing');
|
||||||
|
console.log('Channel:', channel ? channel : 'Missing');
|
||||||
|
|
||||||
|
if (!oauthToken || !username || !channel) {
|
||||||
|
console.error('Missing required environment variables');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.send(`PASS ${oauthToken}`);
|
||||||
|
ws.send(`NICK ${username}`);
|
||||||
|
ws.send(`JOIN #${channel}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
console.log('Received:', event.data);
|
||||||
|
|
||||||
|
const parsedMessage = parseTwitchMessage(event.data);
|
||||||
|
console.log('Parsed Message:', parsedMessage);
|
||||||
|
|
||||||
|
if (parsedMessage.command === '376') {
|
||||||
|
console.log('End of MOTD received. Ready to process messages.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedMessage && parsedMessage.command === 'JOIN') {
|
||||||
|
console.log(`Joined channel: ${parsedMessage.params[0]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedMessage && parsedMessage.command === 'PRIVMSG') {
|
||||||
|
const chatMessage = parsedMessage.params.slice(1).join(' ').trim();
|
||||||
|
const username = parsedMessage.prefix.split('!')[0];
|
||||||
|
console.log('Chat Message:', chatMessage);
|
||||||
|
console.log('Username:', username);
|
||||||
|
|
||||||
|
// Update the regular expression to handle color names with hyphens
|
||||||
|
const commandPattern = /^(?:!place|!p|!paint)\s+([A-P])\s*(\d{1,2})\s+([\w-]+)$/i;
|
||||||
|
const commandMatch = chatMessage.match(commandPattern);
|
||||||
|
console.log('Command Match:', commandMatch);
|
||||||
|
if (commandMatch) {
|
||||||
|
const [, x, y, color] = commandMatch;
|
||||||
|
console.log(`Parsed Command: !place ${x}${y} ${color}`);
|
||||||
|
sendPlaceCommand(chatMessage, username, x, y, color);
|
||||||
|
} else {
|
||||||
|
console.log('No matching command found in the message.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Message is not a PRIVMSG.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = (error) => {
|
||||||
|
console.error('WebSocket Error:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
console.log('WebSocket attempting to reconnect');
|
||||||
|
setTimeout(connectWebSocket, 5000); // Reconnect after 5 seconds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTwitchMessage(message) {
|
||||||
|
console.log('Raw Message:', message);
|
||||||
|
|
||||||
|
const prefixEnd = message.indexOf(' ');
|
||||||
|
const prefix = message.substring(1, prefixEnd);
|
||||||
|
|
||||||
|
const commandEnd = message.indexOf(' ', prefixEnd + 1);
|
||||||
|
const command = message.substring(prefixEnd + 1, commandEnd);
|
||||||
|
|
||||||
|
const params = message.substring(commandEnd + 1).split(' :');
|
||||||
|
const tagsStart = message.indexOf('@');
|
||||||
|
const tags = {};
|
||||||
|
|
||||||
|
if (tagsStart !== -1) {
|
||||||
|
const tagsEnd = message.indexOf(' ', tagsStart + 1);
|
||||||
|
const tagsRaw = message.substring(tagsStart + 1, tagsEnd);
|
||||||
|
tagsRaw.split(';').forEach(tag => {
|
||||||
|
const [key, value] = tag.split('=');
|
||||||
|
tags[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Parsed Tags:', tags);
|
||||||
|
console.log('Parsed Command:', command);
|
||||||
|
console.log('Parsed Params:', params);
|
||||||
|
|
||||||
|
return { prefix, command, params, tags };
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPlaceCommand(chatMessage, username, x, y, color) {
|
||||||
|
console.log(`Sending command to server: !place ${x}${y} ${color}`);
|
||||||
|
fetch('/twitch/parse', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ message: chatMessage, username: username, game_session_id: 1 }) // Adjust game_session_id as needed
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Server Response:', data);
|
||||||
|
if (data && data.command.command === 'place') {
|
||||||
|
console.log('Parsed Place Command from server:', data);
|
||||||
|
colorCell(`${data.command.x}${data.command.y}`, data.color.hex_value);
|
||||||
|
} else {
|
||||||
|
console.error('Invalid command in server response.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Parsing Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the WebSocket when the script loads
|
||||||
|
connectWebSocket();
|
|
@ -0,0 +1,87 @@
|
||||||
|
@extends('layouts.administration-layout')
|
||||||
|
|
||||||
|
@section('title', 'Administration')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h1 class="mb-4">Administration Dashboard</h1>
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="card bg-secondary text-light mb-4">
|
||||||
|
<div class="card-header bg-dark">
|
||||||
|
<h2>Current Game</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if($currentGame)
|
||||||
|
<p><strong>Session Name:</strong> {{ $currentGame->session_name }}</p>
|
||||||
|
<p><strong>Current Palette:</strong> {{ $currentGame->palette ? $currentGame->palette->name : 'None' }}</p>
|
||||||
|
<div class="mt-3">
|
||||||
|
<h3>Current State</h3>
|
||||||
|
<div class="grid-container">
|
||||||
|
@foreach($currentState as $state)
|
||||||
|
<div class="grid-item" style="background-color: {{ $state->color->hex_value }};">
|
||||||
|
({{ $state->x }}, {{ $state->y }})
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p>No active game session.</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-secondary text-light mb-4">
|
||||||
|
<div class="card-header bg-dark">
|
||||||
|
<h2>Change Palette</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ route('administration.changePalette') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="palette_id_change">Select Palette:</label>
|
||||||
|
<select id="palette_id_change" name="palette_id" class="form-control bg-dark text-light" required>
|
||||||
|
@foreach($palettes as $palette)
|
||||||
|
<option value="{{ $palette->id }}" {{ $currentGame && $currentGame->palette_id == $palette->id ? 'selected' : '' }}>
|
||||||
|
{{ $palette->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">Change Palette</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-secondary text-light mb-4">
|
||||||
|
<div class="card-header bg-dark">
|
||||||
|
<h2>Create New Game</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="createGameForm" action="{{ route('administration.createGame') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="session_name">Session Name:</label>
|
||||||
|
<input type="text" id="session_name" name="session_name" class="form-control bg-dark text-light" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="palette_id_create">Select Palette:</label>
|
||||||
|
<select id="palette_id_create" name="palette_id" class="form-control bg-dark text-light" required>
|
||||||
|
@foreach($palettes as $palette)
|
||||||
|
<option value="{{ $palette->id }}">{{ $palette->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success btn-block">Create Game</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user