runware.ai connection test funktioniert, drucken dialog implementiert
This commit is contained in:
@@ -103,17 +103,34 @@ class RunwareAi implements ApiPluginInterface
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$response = Http::withHeaders([
|
$response = Http::withHeaders([
|
||||||
'Authorization' => 'Bearer ' . $token,
|
'Content-Type' => 'application/json',
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
])->timeout(5)->post($apiUrl, [
|
])->timeout(5)->post($apiUrl, [
|
||||||
[
|
[
|
||||||
'taskType' => 'ping',
|
'taskType' => 'authentication',
|
||||||
|
'apiKey' => $token,
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $response->successful();
|
$responseData = $response->json();
|
||||||
|
|
||||||
|
if ($response->successful() && isset($responseData['data']) && !isset($responseData['error'])) {
|
||||||
|
$this->logInfo('RunwareAI connection test successful: Authentication successful.', [
|
||||||
|
'status' => $response->status(),
|
||||||
|
'response' => $responseData,
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$errorMessage = $responseData['error'] ?? 'Unknown error';
|
||||||
|
$this->logError('RunwareAI connection test failed: Authentication failed.', [
|
||||||
|
'status' => $response->status(),
|
||||||
|
'response' => $responseData,
|
||||||
|
'error_message' => $errorMessage,
|
||||||
|
]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logError('RunwareAI connection test failed.', ['error' => $e->getMessage()]);
|
$this->logError('RunwareAI connection test failed: Exception caught.', ['error' => $e->getMessage()]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use Filament\Notifications\Notification;
|
|||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Api\Plugins\PluginLoader;
|
||||||
|
|
||||||
class ApiProviderResource extends Resource
|
class ApiProviderResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -90,67 +91,59 @@ class ApiProviderResource extends Resource
|
|||||||
->action(function (array $data, Forms\Components\Component $component, \Livewire\Component $livewire) {
|
->action(function (array $data, Forms\Components\Component $component, \Livewire\Component $livewire) {
|
||||||
$formData = $component->getLivewire()->form->getState();
|
$formData = $component->getLivewire()->form->getState();
|
||||||
$apiUrl = str_replace('127.0.0.1', 'localhost', $formData['api_url'] ?? null);
|
$apiUrl = str_replace('127.0.0.1', 'localhost', $formData['api_url'] ?? null);
|
||||||
$plugin = $formData['plugin'] ?? null;
|
$pluginName = $formData['plugin'] ?? null;
|
||||||
$username = $formData['username'] ?? null;
|
|
||||||
$password = $formData['password'] ?? null;
|
|
||||||
$token = $formData['token'] ?? null;
|
$token = $formData['token'] ?? null;
|
||||||
|
|
||||||
|
if (!$pluginName) {
|
||||||
|
Notification::make()
|
||||||
|
->title(__('filament.resource.api_provider.notification.connection_failed'))
|
||||||
|
->body('Please select a plugin first.')
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
$component->getLivewire()->dispatch('testConnectionFinished', result: 'failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$http = Http::timeout(25);
|
// Create a dummy ApiProvider model for the test
|
||||||
|
$dummyApiProvider = new \App\Models\ApiProvider();
|
||||||
|
$dummyApiProvider->api_url = $apiUrl;
|
||||||
|
$dummyApiProvider->token = $token;
|
||||||
|
|
||||||
if ($username && $password) {
|
// Load the specific plugin using the PluginLoader
|
||||||
$http->withBasicAuth($username, $password);
|
$pluginInstance = PluginLoader::getPlugin($pluginName, $dummyApiProvider);
|
||||||
} elseif ($token) {
|
|
||||||
$http->withToken($token);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $http->get($apiUrl);
|
// Call the testConnection method of the plugin
|
||||||
|
$testResult = $pluginInstance->testConnection([
|
||||||
|
'api_url' => $apiUrl,
|
||||||
|
'token' => $token,
|
||||||
|
'username' => $formData['username'] ?? null,
|
||||||
|
'password' => $formData['password'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
if ($response->successful()) {
|
if ($testResult) {
|
||||||
Log::info('External API connection successful.', [
|
|
||||||
'api_url' => $apiUrl,
|
|
||||||
'plugin' => $plugin,
|
|
||||||
]);
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title(__('filament.resource.api_provider.notification.connection_successful'))
|
->title(__('filament.resource.api_provider.notification.connection_successful'))
|
||||||
->success()
|
->success()
|
||||||
->send();
|
->send();
|
||||||
$component->getLivewire()->dispatch('testConnectionFinished', result: 'success');
|
$component->getLivewire()->dispatch('testConnectionFinished', result: 'success');
|
||||||
} else {
|
} else {
|
||||||
Log::warning('External API connection failed: Non-successful response.', [
|
|
||||||
'api_url' => $apiUrl,
|
|
||||||
'plugin' => $plugin,
|
|
||||||
'status' => $response->status(),
|
|
||||||
'response_body' => $response->body(),
|
|
||||||
]);
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title(__('filament.resource.api_provider.notification.connection_failed'))
|
->title(__('filament.resource.api_provider.notification.connection_failed'))
|
||||||
->body($response->json('message', 'An unknown error occurred.'))
|
->body('Plugin reported connection failed. Check logs for details.')
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
$component->getLivewire()->dispatch('testConnectionFinished', result: 'failed');
|
$component->getLivewire()->dispatch('testConnectionFinished', result: 'failed');
|
||||||
}
|
}
|
||||||
} catch (\Illuminate\Http\Client\RequestException $e) {
|
|
||||||
Log::error('External API connection failed: Timeout or network error.', [
|
|
||||||
'api_url' => $apiUrl,
|
|
||||||
'plugin' => $plugin,
|
|
||||||
'error_message' => $e->getMessage(),
|
|
||||||
]);
|
|
||||||
Notification::make()
|
|
||||||
->title(__('filament.resource.api_provider.notification.connection_failed'))
|
|
||||||
->body('Timeout or network error: ' . $e->getMessage())
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
$component->getLivewire()->dispatch('testConnectionFinished', result: 'failed');
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('External API connection failed: An unexpected error occurred.', [
|
Log::error('Plugin test connection failed: An unexpected error occurred.', [
|
||||||
'api_url' => $apiUrl,
|
'api_url' => $apiUrl,
|
||||||
'plugin' => $plugin,
|
'plugin' => $pluginName,
|
||||||
'error_message' => $e->getMessage(),
|
'error_message' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title(__('filament.resource.api_provider.notification.connection_failed'))
|
->title(__('filament.resource.api_provider.notification.connection_failed'))
|
||||||
->body('An unexpected error occurred: ' . $e->getMessage())
|
->body('An unexpected error occurred during plugin test: ' . $e->getMessage())
|
||||||
->danger()
|
->danger()
|
||||||
->send();
|
->send();
|
||||||
$component->getLivewire()->dispatch('testConnectionFinished', result: 'failed');
|
$component->getLivewire()->dispatch('testConnectionFinished', result: 'failed');
|
||||||
|
|||||||
@@ -35,4 +35,11 @@ class StyleController extends Controller
|
|||||||
|
|
||||||
return response()->json(['interval' => $interval ? (int)$interval->value / 1000 : 5]);
|
return response()->json(['interval' => $interval ? (int)$interval->value / 1000 : 5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMaxNumberOfCopies()
|
||||||
|
{
|
||||||
|
$maxCopies = Setting::where('key', 'max_number_of_copies')->first();
|
||||||
|
|
||||||
|
return response()->json(['max_copies' => $maxCopies ? (int)$maxCopies->value : 10]); // Default to 10 if not set
|
||||||
|
}
|
||||||
}
|
}
|
||||||
53
app/Http/Controllers/PrintController.php
Normal file
53
app/Http/Controllers/PrintController.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class PrintController extends Controller
|
||||||
|
{
|
||||||
|
public function printImage(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'image_path' => 'required|string',
|
||||||
|
'quantity' => 'required|integer|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$imagePath = public_path(str_replace(url('/'), '', $request->input('image_path')));
|
||||||
|
$quantity = $request->input('quantity');
|
||||||
|
|
||||||
|
if (!file_exists($imagePath)) {
|
||||||
|
Log::error("PrintController: Image file not found at {$imagePath}");
|
||||||
|
return response()->json(['error' => 'Image file not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: Replace this command with one that works in your environment.
|
||||||
|
// Examples:
|
||||||
|
// Linux/macOS: $command = ['lpr', '-#', $quantity, $imagePath];
|
||||||
|
// Windows (assuming a shared printer named 'MyNetworkPrinter'):
|
||||||
|
// $command = ['print', '/d:\\MyNetworkPrinter', $imagePath];
|
||||||
|
// You might need to install additional software or configure your system
|
||||||
|
// to enable command-line printing.
|
||||||
|
// For a more robust solution, consider a dedicated print server application
|
||||||
|
// or a commercial print API.
|
||||||
|
$command = ['echo', "Simulating print of {$quantity} copies of {$imagePath}"]; // Placeholder
|
||||||
|
|
||||||
|
try {
|
||||||
|
$process = new Process($command);
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
if (!$process->isSuccessful()) {
|
||||||
|
throw new ProcessFailedException($process);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info("PrintController: Successfully sent print command for {$imagePath} (x{$quantity})");
|
||||||
|
return response()->json(['message' => 'Print command sent successfully.']);
|
||||||
|
} catch (ProcessFailedException $exception) {
|
||||||
|
Log::error("PrintController: Print command failed. Error: " . $exception->getMessage());
|
||||||
|
return response()->json(['error' => 'Failed to send print command.', 'details' => $exception->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,8 @@ class DatabaseSeeder extends Seeder
|
|||||||
{
|
{
|
||||||
$this->call([
|
$this->call([
|
||||||
RoleSeeder::class,
|
RoleSeeder::class,
|
||||||
UserSeeder::class
|
UserSeeder::class,
|
||||||
|
MaxCopiesSettingSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
database/seeders/MaxCopiesSettingSeeder.php
Normal file
20
database/seeders/MaxCopiesSettingSeeder.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use App\Models\Setting;
|
||||||
|
|
||||||
|
class MaxCopiesSettingSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['key' => 'max_number_of_copies'],
|
||||||
|
['value' => 10] // Default value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,28 +5,23 @@
|
|||||||
<img :src="image.path" :alt="'Selected Image ' + image.name" />
|
<img :src="image.path" :alt="'Selected Image ' + image.name" />
|
||||||
</div>
|
</div>
|
||||||
<div class="context-menu-options">
|
<div class="context-menu-options">
|
||||||
<ul v-if="!showStyleSelectorView">
|
<div v-if="!showStyleSelectorView" class="flex flex-col p-2 space-y-2">
|
||||||
<li @click="$emit('print', image)">
|
<button @click="$emit('close')" class="context-menu-button">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-10 h-10 inline-block align-middle mr-2">
|
||||||
<path fill-rule="evenodd" d="M7.875 1.5C6.839 1.5 6 2.34 6 3.375v2.25a.75.75 0 0 0 1.5 0V3.375c0-.39.315-.75.75-.75h6.75c.39 0 .75.36.75.75v2.25a.75.75 0 0 0 1.5 0V3.375c0-1.036-.84-1.875-1.875-1.875H7.875Z" clip-rule="evenodd" />
|
|
||||||
<path fill-rule="evenodd" d="M19.875 9a.75.75 0 0 0-.75-.75H4.875a.75.75 0 0 0-.75.75v6.75c0 1.036.84 1.875 1.875 1.875h11.25c1.036 0 1.875-.84 1.875-1.875V9ZM12 12.75a.75.75 0 0 0 0 1.5h.007a.75.75 0 0 0 0-1.5H12Z" clip-rule="evenodd" />
|
|
||||||
<path d="M2.25 17.75a.75.75 0 0 0 0 1.5h19.5a.75.75 0 0 0 0-1.5H2.25Z" />
|
|
||||||
</svg>
|
|
||||||
Drucken
|
|
||||||
</li>
|
|
||||||
<li @click="showStyleSelectorView = true">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
|
|
||||||
<path fill-rule="evenodd" d="M10.788 3.212a.75.75 0 0 0-1.06 0L7.212 5.788a.75.75 0 0 0 0 1.06l1.59 1.59a.75.75 0 0 0 1.06 0l2.576-2.576a.75.75 0 0 0 0-1.06L10.788 3.212ZM15.75 10.5a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 12a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM18.75 10.5a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM15.75 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM18.75 15a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75ZM15.75 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM18.75 18a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5h-.75a.75.75 0 0 1-.75-.75ZM12 21a.75.75 0 0 1 .75-.75h.75a.75.75 0 0 1 0 1.5H12a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
Stil ändern
|
|
||||||
</li>
|
|
||||||
<li @click="$emit('close')">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 inline-block align-middle mr-2">
|
|
||||||
<path fill-rule="evenodd" d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M5.47 5.47a.75.75 0 0 1 1.06 0L12 10.94l5.47-5.47a.75.75 0 1 1 1.06 1.06L13.06 12l5.47 5.47a.75.75 0 1 1-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 0 1-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
||||||
|
<path d="M6.22 6.22a.75.75 0 0 1 1.06 0L12 10.94l4.72-4.72a.75.75 0 1 1 1.06 1.06L13.06 12l4.72 4.72a.75.75 0 1 1-1.06 1.06L12 13.06l-4.72 4.72a.75.75 0 0 1-1.06-1.06L10.94 12 6.22 7.28a.75.75 0 0 1 0-1.06Z" />
|
||||||
</svg>
|
</svg>
|
||||||
Schließen
|
Schließen
|
||||||
</li>
|
</button>
|
||||||
</ul>
|
<button @click="$emit('print', image)" class="context-menu-button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class="w-8 h-8 inline-block align-middle mr-2"><!--!Font Awesome Free v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M128 0C92.7 0 64 28.7 64 64l0 96 64 0 0-96 226.7 0L384 93.3l0 66.7 64 0 0-66.7c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0L128 0zM384 352l0 32 0 64-256 0 0-64 0-16 0-16 256 0zm64 32l32 0c17.7 0 32-14.3 32-32l0-96c0-35.3-28.7-64-64-64L64 192c-35.3 0-64 28.7-64 64l0 96c0 17.7 14.3 32 32 32l32 0 0 64c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-64zM432 248a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>
|
||||||
|
Drucken
|
||||||
|
</button>
|
||||||
|
<button @click="showStyleSelectorView = true" class="context-menu-button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class="w-8 h-8 inline-block align-middle mr-2"><!--!Font Awesome Pro v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc.--><path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM183.2 132.6c-1.3-2.8-4.1-4.6-7.2-4.6s-5.9 1.8-7.2 4.6l-16.6 34.7-38.1 5c-3.1 .4-5.6 2.5-6.6 5.5s-.1 6.2 2.1 8.3l27.9 26.5-7 37.8c-.6 3 .7 6.1 3.2 7.9s5.8 2 8.5 .6L176 240.5l33.8 18.3c2.7 1.5 6 1.3 8.5-.6s3.7-4.9 3.2-7.9l-7-37.8L242.4 186c2.2-2.1 3.1-5.3 2.1-8.3s-3.5-5.1-6.6-5.5l-38.1-5-16.6-34.7zm160 0c-1.3-2.8-4.1-4.6-7.2-4.6s-5.9 1.8-7.2 4.6l-16.6 34.7-38.1 5c-3.1 .4-5.6 2.5-6.6 5.5s-.1 6.2 2.1 8.3l27.9 26.5-7 37.8c-.6 3 .7 6.1 3.2 7.9s5.8 2 8.5 .6L336 240.5l33.8 18.3c2.7 1.5 6 1.3 8.5-.6s3.7-4.9 3.2-7.9l-7-37.8L402.4 186c2.2-2.1 3.1-5.3 2.1-8.3s-3.5-5.1-6.6-5.5l-38.1-5-16.6-34.7zm6.3 175.8c-28.9 6.8-60.5 10.5-93.6 10.5s-64.7-3.7-93.6-10.5c-18.7-4.4-35.9 12-25.5 28.1c24.6 38.1 68.7 63.5 119.1 63.5s94.5-25.4 119.1-63.5c10.4-16.1-6.8-32.5-25.5-28.1z"/></svg>
|
||||||
|
Stil ändern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<StyleSelector
|
<StyleSelector
|
||||||
v-else
|
v-else
|
||||||
:image_id="image.image_id"
|
:image_id="image.image_id"
|
||||||
@@ -86,7 +81,7 @@ const showStyleSelectorView = ref(false);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-image-preview {
|
.context-menu-image-preview {
|
||||||
flex: 6; /* Takes 60% of the space (6 out of 10 parts) */
|
flex: 7; /* Takes 60% of the space (6 out of 10 parts) */
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -101,32 +96,23 @@ const showStyleSelectorView = ref(false);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-options {
|
.context-menu-options {
|
||||||
flex: 4; /* Takes 40% of the space (4 out of 10 parts) */
|
flex: 3; /* Takes 40% of the space (4 out of 10 parts) */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border-left: 1px solid var(--color-text); /* Trennlinie zwischen Bild und Menü */
|
border-left: 1px solid var(--color-text); /* Trennlinie zwischen Bild und Menü */
|
||||||
min-width: 150px; /* Mindestbreite für das Menü */
|
min-width: 150px; /* Mindestbreite für das Menü */
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu li {
|
.context-menu-button {
|
||||||
padding: 15px;
|
@apply flex items-center justify-start px-4 py-3 font-medium text-white bg-[#0056b3] hover:bg-[#004494] focus:outline-none focus:ring-2 focus:ring-[#0056b3] focus:ring-offset-2 transition ease-in-out duration-150 rounded-md;
|
||||||
cursor: pointer;
|
width: 70%;
|
||||||
font-size: 1.1em;
|
text-align: left;
|
||||||
border-bottom: 1px solid var(--color-text);
|
border-bottom: 1px solid var(--color-text);
|
||||||
|
margin: 5px auto;
|
||||||
|
font-size: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu li:last-child {
|
.context-menu-button:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu li:hover {
|
|
||||||
@apply hover:bg-gray-100 dark:hover:bg-gray-700;
|
|
||||||
@apply dark:hover:text-gray-400;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
95
resources/js/Components/PrintQuantityModal.vue
Normal file
95
resources/js/Components/PrintQuantityModal.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal-overlay" @click.self="$emit('close')">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h2 class="text-xl font-bold mb-4">{{ __('api.print_dialog.title') }}</h2>
|
||||||
|
<p class="mb-4">{{ __('api.print_dialog.quantity_prompt') }}</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center space-x-4 mb-6">
|
||||||
|
<button @click="decrementQuantity" class="quantity-button">
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<span class="text-5xl font-bold">{{ quantity }}</span>
|
||||||
|
<button @click="incrementQuantity" class="quantity-button">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end space-x-2">
|
||||||
|
<button @click="$emit('close')" class="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">
|
||||||
|
{{ __('api.print_dialog.cancel_button') }}
|
||||||
|
</button>
|
||||||
|
<button @click="confirmPrint" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
||||||
|
{{ __('api.print_dialog.print_button') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
maxCopies: {
|
||||||
|
type: Number,
|
||||||
|
default: 10, // Default max if not provided
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close', 'printConfirmed']);
|
||||||
|
|
||||||
|
const quantity = ref(1);
|
||||||
|
|
||||||
|
const incrementQuantity = () => {
|
||||||
|
if (quantity.value < props.maxCopies) {
|
||||||
|
quantity.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const decrementQuantity = () => {
|
||||||
|
if (quantity.value > 1) {
|
||||||
|
quantity.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmPrint = () => {
|
||||||
|
emit('printConfirmed', quantity.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Watch for changes in maxCopies prop and adjust quantity if it exceeds new max
|
||||||
|
watch(() => props.maxCopies, (newMax) => {
|
||||||
|
if (quantity.value > newMax) {
|
||||||
|
quantity.value = newMax;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--color-background);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-button {
|
||||||
|
@apply bg-blue-500 text-white rounded-full w-16 h-16 flex items-center justify-center text-4xl font-bold;
|
||||||
|
@apply hover:bg-blue-600 transition-colors duration-200;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -39,6 +39,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<LoadingSpinner v-if="isLoading" :progress="processingProgress" />
|
<LoadingSpinner v-if="isLoading" :progress="processingProgress" />
|
||||||
|
<PrintQuantityModal
|
||||||
|
v-if="currentOverlayComponent === 'printQuantityModal'"
|
||||||
|
@close="currentOverlayComponent = null"
|
||||||
|
@printConfirmed="handlePrintConfirmed"
|
||||||
|
:maxCopies="maxCopiesSetting"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -70,6 +76,7 @@ import ImageContextMenu from '../Components/ImageContextMenu.vue';
|
|||||||
import StyleSelector from '../Components/StyleSelector.vue';
|
import StyleSelector from '../Components/StyleSelector.vue';
|
||||||
import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component
|
import StyledImageDisplay from '../Components/StyledImageDisplay.vue'; // Import the new component
|
||||||
import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component
|
import LoadingSpinner from '../Components/LoadingSpinner.vue'; // Import the new component
|
||||||
|
import PrintQuantityModal from '../Components/PrintQuantityModal.vue'; // Import the new component
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
@@ -84,6 +91,7 @@ const processingProgress = ref(0); // To store the progress percentage
|
|||||||
const errorMessage = ref(null); // New ref for error messages
|
const errorMessage = ref(null); // New ref for error messages
|
||||||
const isLoading = ref(false); // New ref for loading state
|
const isLoading = ref(false); // New ref for loading state
|
||||||
const currentTheme = ref('light'); // New ref for current theme
|
const currentTheme = ref('light'); // New ref for current theme
|
||||||
|
const maxCopiesSetting = ref(10); // Default to 10, will be fetched from backend
|
||||||
|
|
||||||
let touchStartX = 0;
|
let touchStartX = 0;
|
||||||
let touchEndX = 0;
|
let touchEndX = 0;
|
||||||
@@ -124,8 +132,27 @@ const showContextMenu = (image, event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const printImage = () => {
|
const printImage = () => {
|
||||||
console.log('Printing image:', selectedImage.value);
|
console.log('Showing print quantity modal for image:', selectedImage.value);
|
||||||
currentOverlayComponent.value = null;
|
currentOverlayComponent.value = 'printQuantityModal';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePrintConfirmed = (quantity) => {
|
||||||
|
console.log(`Printing ${quantity} copies of image:`, selectedImage.value);
|
||||||
|
currentOverlayComponent.value = null; // Close the modal
|
||||||
|
|
||||||
|
axios.post('/api/print-image', {
|
||||||
|
image_id: selectedImage.value.id,
|
||||||
|
image_path: selectedImage.value.path,
|
||||||
|
quantity: quantity,
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log('Print request sent successfully:', response.data);
|
||||||
|
showError('Print request sent successfully!');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error sending print request:', error);
|
||||||
|
showError(error.response?.data?.error || 'Failed to send print request.');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showStyleSelector = () => {
|
const showStyleSelector = () => {
|
||||||
@@ -294,6 +321,15 @@ onMounted(() => {
|
|||||||
console.error('Error fetching image refresh interval:', error);
|
console.error('Error fetching image refresh interval:', error);
|
||||||
fetchInterval = setInterval(fetchImages, 5000); // Fallback to 5 seconds
|
fetchInterval = setInterval(fetchImages, 5000); // Fallback to 5 seconds
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch max number of copies setting from API
|
||||||
|
axios.get('/api/max-copies-setting')
|
||||||
|
.then(response => {
|
||||||
|
maxCopiesSetting.value = response.data.max_copies;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching max copies setting:', error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|||||||
@@ -18,5 +18,10 @@ return [
|
|||||||
'styled_image_display.title' => 'Neu gestyltes Bild',
|
'styled_image_display.title' => 'Neu gestyltes Bild',
|
||||||
'styled_image_display.keep_button' => 'Behalten',
|
'styled_image_display.keep_button' => 'Behalten',
|
||||||
'styled_image_display.delete_button' => 'Löschen',
|
'styled_image_display.delete_button' => 'Löschen',
|
||||||
|
'print_command_sent_successfully' => 'Druckbefehl erfolgreich gesendet.',
|
||||||
|
'failed_to_send_print_command' => 'Druckbefehl konnte nicht gesendet werden.',
|
||||||
|
'api.print_dialog.title' => 'Bild drucken',
|
||||||
|
'api.print_dialog.quantity_prompt' => 'Wie viele Kopien möchten Sie drucken?',
|
||||||
|
'api.print_dialog.cancel_button' => 'Abbrechen',
|
||||||
|
'api.print_dialog.print_button' => 'Drucken',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,4 +18,10 @@ return [
|
|||||||
'styled_image_display.title' => 'Newly Styled Image',
|
'styled_image_display.title' => 'Newly Styled Image',
|
||||||
'styled_image_display.keep_button' => 'Keep',
|
'styled_image_display.keep_button' => 'Keep',
|
||||||
'styled_image_display.delete_button' => 'Delete',
|
'styled_image_display.delete_button' => 'Delete',
|
||||||
|
'print_command_sent_successfully' => 'Print command sent successfully.',
|
||||||
|
'failed_to_send_print_command' => 'Failed to send print command.',
|
||||||
|
'print_dialog.title' => 'Print Image',
|
||||||
|
'print_dialog.quantity_prompt' => 'How many copies would you like to print?',
|
||||||
|
'print_dialog.cancel_button' => 'Cancel',
|
||||||
|
'print_dialog.print_button' => 'Print',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Route::post('/admin/navigation-state', [NavigationStateController::class, 'store
|
|||||||
Route::get('/images', [ImageController::class, 'index']);
|
Route::get('/images', [ImageController::class, 'index']);
|
||||||
Route::get('/styles', [StyleController::class, 'index']);
|
Route::get('/styles', [StyleController::class, 'index']);
|
||||||
Route::get('/image-refresh-interval', [StyleController::class, 'getImageRefreshInterval']);
|
Route::get('/image-refresh-interval', [StyleController::class, 'getImageRefreshInterval']);
|
||||||
|
Route::get('/max-copies-setting', [StyleController::class, 'getMaxNumberOfCopies']);
|
||||||
|
|
||||||
Route::post('/images/style-change', [ImageController::class, 'styleChangeRequest']);
|
Route::post('/images/style-change', [ImageController::class, 'styleChangeRequest']);
|
||||||
Route::get('/comfyui-url', [ImageController::class, 'getComfyUiUrl']);
|
Route::get('/comfyui-url', [ImageController::class, 'getComfyUiUrl']);
|
||||||
@@ -39,3 +40,6 @@ Route::middleware('auth:sanctum')->group(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/images/fetch-styled/{prompt_id}', [ImageController::class, 'fetchStyledImage']);
|
Route::get('/images/fetch-styled/{prompt_id}', [ImageController::class, 'fetchStyledImage']);
|
||||||
|
|
||||||
|
use App\Http\Controllers\PrintController;
|
||||||
|
Route::post('/print-image', [PrintController::class, 'printImage']);
|
||||||
|
|||||||
Reference in New Issue
Block a user