mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
349e2828eb
@ -1 +1 @@
|
||||
5.6.17
|
||||
5.6.18
|
@ -318,6 +318,7 @@ class BaseExport
|
||||
|
||||
private function resolvePaymentKey($column, $entity, $transformer)
|
||||
{
|
||||
|
||||
if($entity instanceof Payment){
|
||||
|
||||
$transformed_payment = $transformer->transform($entity);
|
||||
|
@ -136,8 +136,11 @@ class InvitationController extends Controller
|
||||
} else {
|
||||
$is_silent = 'true';
|
||||
|
||||
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent]);
|
||||
|
||||
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key}), 'silent' => $is_silent])->header('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
}
|
||||
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})]);
|
||||
|
||||
return redirect()->route('client.'.$entity.'.show', [$entity => $this->encodePrimaryKey($invitation->{$key})])->header('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
|
||||
}
|
||||
|
@ -206,8 +206,6 @@ class InvoiceController extends Controller
|
||||
|
||||
$file = $invoice->service()->getInvoicePdf(auth()->guard('contact')->user());
|
||||
|
||||
// return response()->download(file_get_contents(public_path($file)));
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
|
@ -32,6 +32,8 @@ class MigrationController extends BaseController
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
public bool $silent_migration = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
@ -260,6 +262,9 @@ class MigrationController extends BaseController
|
||||
{
|
||||
nlog('Starting Migration');
|
||||
|
||||
if($request->has('silent_migration'))
|
||||
$this->silent_migration = true;
|
||||
|
||||
if ($request->companies) {
|
||||
//handle Laravel 5.5 UniHTTP
|
||||
$companies = json_decode($request->companies, 1);
|
||||
@ -312,7 +317,9 @@ class MigrationController extends BaseController
|
||||
$nmo->company = $user->account->companies()->first();
|
||||
$nmo->settings = $user->account->companies()->first()->settings;
|
||||
$nmo->to_user = $user;
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
if(!$this->silent_migration)
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
return;
|
||||
} elseif ($existing_company && $company_count > 10) {
|
||||
@ -321,7 +328,9 @@ class MigrationController extends BaseController
|
||||
$nmo->company = $user->account->companies()->first();
|
||||
$nmo->settings = $user->account->companies()->first()->settings;
|
||||
$nmo->to_user = $user;
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
if(!$this->silent_migration)
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -341,7 +350,8 @@ class MigrationController extends BaseController
|
||||
$nmo->settings = $user->account->companies()->first();
|
||||
$nmo->to_user = $user;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
if(!$this->silent_migration)
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
return response()->json([
|
||||
'_id' => Str::uuid(),
|
||||
@ -431,9 +441,9 @@ class MigrationController extends BaseController
|
||||
nlog($migration_file);
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
StartMigration::dispatch($migration_file, $user, $fresh_company)->onQueue('migration');
|
||||
StartMigration::dispatch($migration_file, $user, $fresh_company, $this->silent_migration)->onQueue('migration');
|
||||
} else {
|
||||
StartMigration::dispatch($migration_file, $user, $fresh_company);
|
||||
StartMigration::dispatch($migration_file, $user, $fresh_company, $this->silent_migration);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,7 @@ class ProtectedDownloadController extends BaseController
|
||||
abort(404, 'File no longer available');
|
||||
}
|
||||
|
||||
UnlinkFile::dispatch(config('filesystems.default'), $hashed_path)->delay(now()->addSeconds(10));
|
||||
|
||||
return response()->streamDownload(function () use ($hashed_path) {
|
||||
echo Storage::get($hashed_path);
|
||||
}, basename($hashed_path), []);
|
||||
return response()->download($hashed_path, basename($hashed_path), [])->deleteFileAfterSend(true);
|
||||
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ class WebCronController extends Controller
|
||||
* @return Response
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/webcron",
|
||||
* path="/webcron",
|
||||
* operationId="webcron",
|
||||
* tags={"webcron"},
|
||||
* summary="Executes the task scheduler via a webcron service",
|
||||
|
@ -12,8 +12,21 @@
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Utils\Number;
|
||||
use Livewire\Component;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Services\Pdf\PdfBuilder;
|
||||
use App\Services\Pdf\PdfService;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Services\Pdf\PdfDesigner;
|
||||
use App\Services\Pdf\PdfConfiguration;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
|
||||
|
||||
class PdfSlot extends Component
|
||||
{
|
||||
@ -27,23 +40,212 @@ class PdfSlot extends Component
|
||||
|
||||
public $url;
|
||||
|
||||
private $settings;
|
||||
|
||||
private $html_variables;
|
||||
|
||||
private $entity_type;
|
||||
|
||||
protected $listeners = ['viewportChanged' => 'getPdf'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->db);
|
||||
}
|
||||
|
||||
public function getPdf()
|
||||
{
|
||||
$this->pdf = $this->entity->fullscreenPdfViewer($this->invitation);
|
||||
}
|
||||
|
||||
public function downloadPdf()
|
||||
{
|
||||
|
||||
$file_name = $this->entity->numberFormatter().'.pdf';
|
||||
|
||||
if($this->entity instanceof \App\Models\PurchaseOrder)
|
||||
$file = (new CreatePurchaseOrderPdf($this->invitation, $this->invitation->company->db))->rawPdf();
|
||||
else
|
||||
$file = (new \App\Jobs\Entity\CreateRawPdf($this->invitation, $this->invitation->company->db))->handle();
|
||||
|
||||
$headers = ['Content-Type' => 'application/pdf'];
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->entity_type = $this->resolveEntityType();
|
||||
|
||||
$this->settings = $this->entity->client ? $this->entity->client->getMergedSettings() : $this->entity->company->settings;
|
||||
|
||||
$this->html_variables = $this->entity->client ?
|
||||
(new HtmlEngine($this->invitation))->generateLabelsAndValues() :
|
||||
(new VendorHtmlEngine($this->invitation))->generateLabelsAndValues();
|
||||
|
||||
return render('components.livewire.pdf-slot', [
|
||||
'invitation' => $this->invitation,
|
||||
'entity' => $this->entity,
|
||||
'data' => $this->invitation->company->settings,
|
||||
'entity_type' => $this->entity_type,
|
||||
'products' => $this->getProducts(),
|
||||
'services' => $this->getServices(),
|
||||
'amount' => Number::formatMoney($this->entity->amount, $this->entity->client ?: $this->entity->vendor),
|
||||
'balance' => Number::formatMoney($this->entity->balance, $this->entity->client ?: $this->entity->vendor),
|
||||
'company_details' => $this->getCompanyDetails(),
|
||||
'company_address' => $this->getCompanyAddress(),
|
||||
'entity_details' => $this->getEntityDetails(),
|
||||
'user_details' => $this->getUserDetails(),
|
||||
'user_name' => $this->getUserName(),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
public function getPdf()
|
||||
private function convertVariables($string): string
|
||||
{
|
||||
|
||||
$this->pdf = $this->entity->fullscreenPdfViewer($this->invitation);
|
||||
$html = strtr($string, $this->html_variables['labels']);
|
||||
$html = strtr($html, $this->html_variables['values']);
|
||||
|
||||
return $html;
|
||||
|
||||
}
|
||||
|
||||
private function getCompanyAddress()
|
||||
{
|
||||
|
||||
$company_address = "";
|
||||
|
||||
foreach($this->settings->pdf_variables->company_address as $variable) {
|
||||
$company_address .= "<p>{$variable}</p>";
|
||||
}
|
||||
|
||||
return $this->convertVariables($company_address);
|
||||
|
||||
}
|
||||
|
||||
private function getCompanyDetails()
|
||||
{
|
||||
$company_details = "";
|
||||
|
||||
foreach($this->settings->pdf_variables->company_details as $variable) {
|
||||
$company_details .= "<p>{$variable}</p>";
|
||||
}
|
||||
|
||||
return $this->convertVariables($company_details);
|
||||
|
||||
}
|
||||
|
||||
private function getEntityDetails()
|
||||
{
|
||||
$entity_details = "";
|
||||
|
||||
if($this->entity_type == 'invoice' || $this->entity_type == 'recurring_invoice') {
|
||||
foreach($this->settings->pdf_variables->invoice_details as $variable)
|
||||
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
|
||||
|
||||
}
|
||||
elseif($this->entity_type == 'quote'){
|
||||
foreach($this->settings->pdf_variables->quote_details as $variable)
|
||||
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
|
||||
}
|
||||
elseif($this->entity_type == 'credit') {
|
||||
foreach($this->settings->pdf_variables->credit_details as $variable)
|
||||
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
|
||||
}
|
||||
elseif($this->entity_type == 'purchase_order'){
|
||||
foreach($this->settings->pdf_variables->purchase_order_details as $variable)
|
||||
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
|
||||
}
|
||||
|
||||
return $this->convertVariables($entity_details);
|
||||
|
||||
}
|
||||
|
||||
private function getUserName()
|
||||
{
|
||||
$name = ctrans('texts.details');
|
||||
|
||||
if($this->entity_type == 'purchase_order' && isset($this->settings->pdf_variables->vendor_details[0])) {
|
||||
$name = $this->settings->pdf_variables->vendor_details[0];
|
||||
|
||||
} elseif(isset($this->settings->pdf_variables->client_details[0])) {
|
||||
|
||||
$name = $this->settings->pdf_variables->client_details[0];
|
||||
}
|
||||
|
||||
return $this->convertVariables($name);
|
||||
|
||||
}
|
||||
|
||||
private function getUserDetails()
|
||||
{
|
||||
$user_details = "";
|
||||
|
||||
if($this->entity_type == 'purchase_order') {
|
||||
foreach(array_slice($this->settings->pdf_variables->vendor_details,1) as $variable) {
|
||||
$user_details .= "<p>{$variable}</p>";
|
||||
}
|
||||
}
|
||||
else{
|
||||
foreach(array_slice($this->settings->pdf_variables->client_details,1) as $variable) {
|
||||
$user_details .= "<p>{$variable}</p>";
|
||||
}
|
||||
}
|
||||
|
||||
return $this->convertVariables($user_details);
|
||||
}
|
||||
|
||||
private function getProducts()
|
||||
{
|
||||
$product_items = collect($this->entity->line_items)->filter(function ($item) {
|
||||
return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5;
|
||||
})->map(function ($item){
|
||||
return [
|
||||
'quantity' => $item->quantity,
|
||||
'cost' => Number::formatMoney($item->cost, $this->entity->client ?: $this->entity->vendor),
|
||||
'notes' => $item->notes,
|
||||
'line_total' => Number::formatMoney($item->line_total, $this->entity->client ?: $this->entity->vendor),
|
||||
];
|
||||
});
|
||||
|
||||
return $product_items;
|
||||
}
|
||||
|
||||
private function getServices()
|
||||
{
|
||||
$task_items = collect($this->entity->line_items)->filter(function ($item) {
|
||||
return $item->type_id == 2;
|
||||
})->map(function ($item){
|
||||
return [
|
||||
'quantity' => $item->quantity,
|
||||
'cost' => Number::formatMoney($item->cost, $this->entity->client ?: $this->entity->vendor),
|
||||
'notes' => $item->notes,
|
||||
'line_total' => Number::formatMoney($item->line_total, $this->entity->client ?: $this->entity->vendor),
|
||||
];
|
||||
});
|
||||
|
||||
return $task_items;
|
||||
|
||||
}
|
||||
|
||||
private function resolveEntityType() :string
|
||||
{
|
||||
if ($this->invitation instanceof InvoiceInvitation) {
|
||||
return 'invoice';
|
||||
} elseif ($this->invitation instanceof QuoteInvitation) {
|
||||
return 'quote';
|
||||
} elseif ($this->invitation instanceof CreditInvitation) {
|
||||
return 'credit';
|
||||
} elseif ($this->invitation instanceof RecurringInvoiceInvitation) {
|
||||
return 'recurring_invoice';
|
||||
} elseif ($this->invitation instanceof PurchaseOrderInvitation) {
|
||||
return 'purchase_order';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class BulkInvoiceRequest extends Request
|
||||
{
|
||||
return [
|
||||
'action' => 'required|string',
|
||||
'ids' => 'required',
|
||||
'ids' => 'required|array',
|
||||
'email_type' => 'sometimes|in:reminder1,reminder2,reminder3,reminder_endless,custom1,custom2,custom3,invoice,quote,credit,payment,payment_partial,statement,purchase_order'
|
||||
];
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ class BulkInvoiceJob implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{ //only the reminder should mark the reminder sent field
|
||||
// $this->invoice->service()->touchReminder($this->reminder_template)->markSent()->save();
|
||||
|
||||
$this->invoice->service()->markSent()->save();
|
||||
|
||||
$this->invoice->invitations->load('contact.client.country', 'invoice.client.country', 'invoice.company')->each(function ($invitation) {
|
||||
|
@ -56,7 +56,7 @@ class CleanStaleInvoiceOrder implements ShouldQueue
|
||||
Invoice::query()
|
||||
->withTrashed()
|
||||
->where('status_id', Invoice::STATUS_SENT)
|
||||
->where('created_at', '<', now()->subHours(2))
|
||||
->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(10)])
|
||||
->where('balance', '>', 0)
|
||||
->cursor()
|
||||
->each(function ($invoice){
|
||||
@ -77,7 +77,7 @@ class CleanStaleInvoiceOrder implements ShouldQueue
|
||||
Invoice::query()
|
||||
->withTrashed()
|
||||
->where('is_proforma', 1)
|
||||
->where('created_at', '<', now()->subHour())
|
||||
->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(10)])
|
||||
->cursor()
|
||||
->each(function ($invoice) use ($repo) {
|
||||
$invoice->is_proforma = false;
|
||||
|
@ -165,6 +165,8 @@ class Import implements ShouldQueue
|
||||
|
||||
public $timeout = 10000000;
|
||||
|
||||
public $silent_migration;
|
||||
|
||||
// public $backoff = 86430;
|
||||
|
||||
// public $maxExceptions = 2;
|
||||
@ -176,12 +178,13 @@ class Import implements ShouldQueue
|
||||
* @param User $user
|
||||
* @param array $resources
|
||||
*/
|
||||
public function __construct(string $file_path, Company $company, User $user, array $resources = [])
|
||||
public function __construct(string $file_path, Company $company, User $user, array $resources = [], $silent_migration = false)
|
||||
{
|
||||
$this->file_path = $file_path;
|
||||
$this->company = $company;
|
||||
$this->user = $user;
|
||||
$this->resources = $resources;
|
||||
$this->silent_migration = $silent_migration;
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
@ -263,8 +266,9 @@ class Import implements ShouldQueue
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
Mail::to($this->user->email, $this->user->name())
|
||||
->send(new MigrationCompleted($this->company->id, $this->company->db, implode("<br>", $check_data)));
|
||||
if(!$this->silent_migration)
|
||||
Mail::to($this->user->email, $this->user->name())->send(new MigrationCompleted($this->company->id, $this->company->db, implode("<br>", $check_data)));
|
||||
|
||||
} catch(\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
@ -641,7 +645,6 @@ class Import implements ShouldQueue
|
||||
|
||||
$user = $user_repository->save($modified, $this->fetchUser($resource['email']), true, true);
|
||||
$user->email_verified_at = now();
|
||||
// $user->confirmation_code = '';
|
||||
|
||||
if ($modified['deleted_at']) {
|
||||
$user->deleted_at = now();
|
||||
@ -1590,7 +1593,9 @@ class Import implements ShouldQueue
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
$nmo->to_user = $this->user;
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
if(!$this->silent_migration)
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
$modified['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34';
|
||||
}
|
||||
|
@ -99,7 +99,6 @@ class ReminderJob implements ShouldQueue
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('invitations')->chunk(50, function ($invoices) {
|
||||
// if ($invoice->refresh() && $invoice->isPayable()) {
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$this->sendReminderForInvoice($invoice);
|
||||
|
@ -49,6 +49,8 @@ class StartMigration implements ShouldQueue
|
||||
*/
|
||||
private $company;
|
||||
|
||||
private $silent_migration;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -60,11 +62,12 @@ class StartMigration implements ShouldQueue
|
||||
|
||||
public $timeout = 0;
|
||||
|
||||
public function __construct($filepath, User $user, Company $company)
|
||||
public function __construct($filepath, User $user, Company $company, $silent_migration = false)
|
||||
{
|
||||
$this->filepath = $filepath;
|
||||
$this->user = $user;
|
||||
$this->company = $company;
|
||||
$this->silent_migration = $silent_migration;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,7 +119,7 @@ class StartMigration implements ShouldQueue
|
||||
throw new NonExistingMigrationFile('Migration file does not exist, or it is corrupted.');
|
||||
}
|
||||
|
||||
(new Import($file, $this->company, $this->user))->handle();
|
||||
(new Import($file, $this->company, $this->user, [], $this->silent_migration))->handle();
|
||||
|
||||
Storage::deleteDirectory(public_path("storage/migrations/{$filename}"));
|
||||
|
||||
@ -138,7 +141,8 @@ class StartMigration implements ShouldQueue
|
||||
app('sentry')->captureException($e);
|
||||
}
|
||||
|
||||
Mail::to($this->user->email, $this->user->name())->send(new MigrationFailed($e, $this->company, $e->getMessage()));
|
||||
if(!$this->silent_migration)
|
||||
Mail::to($this->user->email, $this->user->name())->send(new MigrationFailed($e, $this->company, $e->getMessage()));
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$migration_failed = new MigrationFailed($e, $this->company, $e->getMessage());
|
||||
|
@ -39,7 +39,7 @@ class UserRepository extends BaseRepository
|
||||
* @param bool $unset_company_user
|
||||
* @return \App\Models\User user Object
|
||||
*/
|
||||
public function save(array $data, User $user, $unset_company_user = false)
|
||||
public function save(array $data, User $user, $unset_company_user = false, $is_migrating = false)
|
||||
{
|
||||
$details = $data;
|
||||
|
||||
@ -71,7 +71,7 @@ class UserRepository extends BaseRepository
|
||||
$user->password = Hash::make($data['password']);
|
||||
}
|
||||
|
||||
if (! $user->confirmation_code) {
|
||||
if (! $user->confirmation_code && !$is_migrating) {
|
||||
$user->confirmation_code = $this->createDbHash($company->db);
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ class InstantPayment
|
||||
|
||||
/* Schedule a job to check the gateway fees for this invoice*/
|
||||
if (Ninja::isHosted()) {
|
||||
CheckGatewayFee::dispatch($first_invoice->id, $client->company->db)->delay(600);
|
||||
CheckGatewayFee::dispatch($first_invoice->id, $client->company->db)->delay(800);
|
||||
}
|
||||
|
||||
if ($gateway) {
|
||||
|
@ -44,6 +44,7 @@ class HandleRestore extends AbstractService
|
||||
//cannot restore an invoice with a deleted payment
|
||||
foreach ($this->invoice->payments as $payment) {
|
||||
if (($this->invoice->paid_to_date == 0) && $payment->is_deleted) {
|
||||
$this->invoice->delete(); //set it back to deleted so that it can be restored from repository
|
||||
return $this->invoice;
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,12 @@ class PdfBuilder
|
||||
|
||||
$this->document = $document;
|
||||
|
||||
// $this->xpath = new DOMXPath($document);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDocument($document): self
|
||||
{
|
||||
$this->document = $document;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -131,6 +136,13 @@ class PdfBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSections($sections): self
|
||||
{
|
||||
$this->sections = $sections;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates delivery note sections
|
||||
*
|
||||
@ -1641,6 +1653,7 @@ class PdfBuilder
|
||||
|
||||
public function updateVariables()
|
||||
{
|
||||
|
||||
$html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']);
|
||||
|
||||
$html = strtr($html, $this->service->html_variables['values']);
|
||||
|
@ -61,7 +61,7 @@ class AccountTransformer extends EntityTransformer
|
||||
'plan_paid' => (string) $account->plan_paid,
|
||||
'plan_expires' => (string) $account->plan_expires,
|
||||
'user_agent' => (string) $account->user_agent,
|
||||
'payment_id' => (string) $account->payment_id,
|
||||
'payment_id' => (string) $this->encodePrimaryKey($account->payment_id),
|
||||
'trial_started' => (string) $account->trial_started,
|
||||
'trial_plan' => (string) $account->trial_plan,
|
||||
'plan_price' => (float) $account->plan_price,
|
||||
|
@ -56,8 +56,35 @@ class Helpers
|
||||
public function formatCustomFieldValue($custom_fields, $field, $value, $entity = null): ?string
|
||||
{
|
||||
$custom_field = '';
|
||||
$quote_or_credit_field = false;
|
||||
|
||||
if ($custom_fields && property_exists($custom_fields, $field)) {
|
||||
if($custom_fields && stripos($field, 'quote') !== false && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
$custom_field_parts = explode('|', $custom_field);
|
||||
|
||||
if (count($custom_field_parts) >= 2) {
|
||||
$custom_field = $custom_field_parts[1];
|
||||
}
|
||||
|
||||
$quote_or_credit_field = true;
|
||||
|
||||
}elseif($custom_fields && stripos($field, 'credit') !== false && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
$custom_field_parts = explode('|', $custom_field);
|
||||
|
||||
if (count($custom_field_parts) >= 2) {
|
||||
$custom_field = $custom_field_parts[1];
|
||||
}
|
||||
|
||||
$quote_or_credit_field = true;
|
||||
|
||||
}elseif($custom_fields && stripos($field, 'credit') !== false) {
|
||||
$field = str_replace("credit", "invoice", $field);
|
||||
}elseif($custom_fields && stripos($field, 'quote') !== false) {
|
||||
$field = str_replace("quote", "invoice", $field);
|
||||
}
|
||||
|
||||
if (!$quote_or_credit_field && $custom_fields && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
$custom_field_parts = explode('|', $custom_field);
|
||||
|
||||
@ -90,6 +117,17 @@ class Helpers
|
||||
*/
|
||||
public function makeCustomField($custom_fields, $field): string
|
||||
{
|
||||
|
||||
if ($custom_fields && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
|
||||
$custom_field_parts = explode('|', $custom_field);
|
||||
|
||||
return $custom_field_parts[0];
|
||||
}
|
||||
|
||||
$field = str_replace(["quote","credit"], ["invoice", "invoice"], $field);
|
||||
|
||||
if ($custom_fields && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
|
||||
|
@ -222,10 +222,10 @@ class HtmlEngine
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
|
||||
$data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$quote.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$quote.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$quote.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
$data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'quote1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'quote1')];
|
||||
$data['$quote.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'quote2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'quote2')];
|
||||
$data['$quote.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'quote3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'quote3')];
|
||||
$data['$quote.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'quote4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'quote4')];
|
||||
|
||||
$data['$custom1'] = &$data['$quote.custom1'];
|
||||
$data['$custom2'] = &$data['$quote.custom2'];
|
||||
@ -266,10 +266,10 @@ class HtmlEngine
|
||||
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.credit_date')];
|
||||
|
||||
$data['$credit.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$credit.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$credit.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$credit.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
$data['$credit.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'credit1')];
|
||||
$data['$credit.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'credit2')];
|
||||
$data['$credit.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'credit3')];
|
||||
$data['$credit.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'credit4')];
|
||||
|
||||
$data['$custom1'] = &$data['$credit.custom1'];
|
||||
$data['$custom2'] = &$data['$credit.custom2'];
|
||||
|
@ -15,8 +15,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION','5.6.17'),
|
||||
'app_tag' => env('APP_TAG','5.6.17'),
|
||||
'app_version' => env('APP_VERSION','5.6.18'),
|
||||
'app_tag' => env('APP_TAG','5.6.18'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,226 @@
|
||||
|
||||
<style>
|
||||
|
||||
table, th, td {
|
||||
/* border: 1px solid black; */
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td
|
||||
{
|
||||
max-width: 1px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
padding: 3px;
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="w-full bg-white py-3 border-2 shadow sm:rounded-lg">
|
||||
|
||||
<div class="px-3 border-fuchsia-600 border-b-2 pb-3">
|
||||
|
||||
<div id="company-details" class="mx-auto">
|
||||
{!! $company_details !!}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="border-fuchsia-600 border-b-2 pb-3 mt-3">
|
||||
|
||||
<div id="entity-details"> {!! $entity_details !!} </div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="user-details" class="mt-3 px-3 border-b-2 border-fuschia-600 flex flex-col items-end">
|
||||
|
||||
<div x-data="{ show_user: false }" class="mb-3">
|
||||
|
||||
<button @click="show_user = !show_user" :aria-expanded="show_user ? 'true' : 'false'" :class="{ 'active': show_user }" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center">
|
||||
<span class="overflow-ellipsis overflow-hidden">{{ $user_name }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>
|
||||
</button>
|
||||
|
||||
<div id="terms" class="py-3" x-show="show_user">
|
||||
{!! $user_details !!}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@if($products->count() > 0)
|
||||
<div id="product-details" class="py-6 mr-5 ml-5">
|
||||
<table width="100%">
|
||||
<thead>
|
||||
<tr class="border-b-2">
|
||||
<th style="text-align:left; width:70%; padding-left:2px;">Item</th>
|
||||
<th style="text-align:right; width:30%; padding-right:2px;">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($products as $product)
|
||||
<tr style="display: table-row;" class="border-b-2">
|
||||
<td>
|
||||
<div class="product-information">
|
||||
<div class="item-details">
|
||||
<p class="mt-2">{{ $product['quantity'] }} × {{ $product['cost'] }}</p>
|
||||
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{{ $product['notes'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="text-align:right; padding-right:2px;">{{ $product['line_total'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
@if($services->count() > 0)
|
||||
<div id="task-details" class="py-6 mr-3 ml-3">
|
||||
<table width="100%">
|
||||
<thead>
|
||||
<tr class="border-bottom">
|
||||
<th style="text-align:left; width:70%; padding-left:2px;">Service</th>
|
||||
<th style="text-align:right; width:30%; padding-right:2px;">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($services as $service)
|
||||
<tr style="display: table-row;">
|
||||
<td>
|
||||
<div class="">
|
||||
<div class="">
|
||||
<p class="mt-2">{{ $service['quantity'] }} × {{ $service['cost'] }}</p>
|
||||
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{{ $service['notes'] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="text-align:right; padding-right:2px;">{{ $service['line_total'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
<div id="totals" class="mb-10 mr-3 ml-3">
|
||||
<table width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align:left; padding-right:10px;" class="text-lg">{{ ctrans('texts.total') }}</td>
|
||||
<td style="text-align:right; padding-right:10px;" class="text-lg">{{ $amount }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:left; padding-right:10px;" class="text-lg">{{ ctrans('texts.balance') }}</td>
|
||||
<td style="text-align:right; padding-right:10px;" class="text-lg">{{ $balance }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
@if(strlen($entity->public_notes) > 3)
|
||||
<div x-data="{ show_notes: false }" class="mb-10 mr-5 ml-5 flex flex-col items-end">
|
||||
|
||||
<button @click="show_notes = !show_notes" :aria-expanded="show_notes ? 'true' : 'false'" :class="{ 'active': show_notes }" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center">
|
||||
<span>{{ ctrans('texts.notes') }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>
|
||||
</button>
|
||||
|
||||
<div id="notes" class="py-10 border-b-2 border-fuschia-600" x-show="show_notes">
|
||||
{{ strip_tags($entity->public_notes) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(strlen($entity->terms) > 3)
|
||||
<div x-data="{ show_terms: false }" class="mb-10 mr-5 ml-5 flex flex-col items-end">
|
||||
|
||||
<button @click="show_terms = !show_terms" :aria-expanded="show_terms ? 'true' : 'false'" :class="{ 'active': show_terms }" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center">
|
||||
<span>{{ ctrans('texts.terms') }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>
|
||||
</button>
|
||||
|
||||
<div id="terms" class="py-10 border-b-2 border-fuschia-600" x-show="show_terms">
|
||||
{{ strip_tags($entity->terms) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(strlen($entity->footer) > 3)
|
||||
<div x-data="{ show_footer: false }" class="mb-10 mr-5 ml-5 flex flex-col items-end">
|
||||
|
||||
<button @click="show_footer = !show_footer" :aria-expanded="show_footer ? 'true' : 'false'" :class="{ 'active': show_footer }" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold py-2 px-4 rounded inline-flex items-center">
|
||||
<span>{{ ctrans('texts.footer') }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>
|
||||
</button>
|
||||
|
||||
<div id="terms" class="py-10 border-b-2 border-fuschia-600" x-show="show_footer">
|
||||
{{ strip_tags($entity->footer) }}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
Array.from(document.getElementsByClassName("entity-field")).forEach(function(item) {
|
||||
if(item.innerText.length == 0){
|
||||
item.parentNode.remove();
|
||||
}
|
||||
});
|
||||
|
||||
Livewire.hook('message.processed', (message, component) => {
|
||||
|
||||
Array.from(document.getElementsByClassName("entity-field")).forEach(function(item) {
|
||||
if(item.innerText.length == 0){
|
||||
item.parentNode.remove();
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
var timeout = false;
|
||||
|
||||
/* Watch for resize of window and ensure we unset props with no values */
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(getDimensions, 250);
|
||||
});
|
||||
|
||||
getDimensions();
|
||||
|
||||
function getDimensions() {
|
||||
|
||||
const width = window.innerWidth;
|
||||
|
||||
if(width < 900){
|
||||
|
||||
Array.from(document.getElementsByClassName("entity-field")).forEach(function(item) {
|
||||
if(item.innerText.length == 0){
|
||||
item.parentNode.remove();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
@ -1,43 +1,63 @@
|
||||
<div wire:init="getPdf()">
|
||||
@if($pdf)
|
||||
<iframe id="pdf-iframe" src="{!! $pdf !!}" class="h-screen w-full border-0 mt-4"></iframe>
|
||||
@else
|
||||
<div class="flex mt-4 place-items-center">
|
||||
<span class="loader m-auto"></span>
|
||||
<style type="text/css">
|
||||
.loader {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
animation: rotate 1s linear infinite
|
||||
}
|
||||
.loader::before , .loader::after {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid #454545;
|
||||
animation: prixClipFix 2s linear infinite ;
|
||||
}
|
||||
.loader::after{
|
||||
border-color: #FF3D00;
|
||||
animation: prixClipFix 2s linear infinite , rotate 0.5s linear infinite reverse;
|
||||
inset: 6px;
|
||||
}
|
||||
@keyframes rotate {
|
||||
0% {transform: rotate(0deg)}
|
||||
100% {transform: rotate(360deg)}
|
||||
}
|
||||
@keyframes prixClipFix {
|
||||
0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)}
|
||||
25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)}
|
||||
50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)}
|
||||
75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)}
|
||||
100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)}
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<div class="flex flex-col items-end mb-2">
|
||||
<button wire:loading.attr="disabled" wire:click="downloadPdf" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold px-2 rounded inline-flex">
|
||||
<span>{{ ctrans('texts.download_pdf') }}</span>
|
||||
<div wire:loading wire:target="downloadPdf">
|
||||
<svg class="animate-spin h-5 w-5 text-blue" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hidden lg:block">
|
||||
<div wire:init="getPdf()">
|
||||
@if($pdf)
|
||||
<iframe id="pdf-iframe" src="{!! $pdf !!}" class="h-screen w-full border-0 mt-4"></iframe>
|
||||
@else
|
||||
<div class="flex mt-4 place-items-center">
|
||||
<span class="loader m-auto"></span>
|
||||
<style type="text/css">
|
||||
.loader {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
animation: rotate 1s linear infinite
|
||||
}
|
||||
.loader::before , .loader::after {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
border-radius: 50%;
|
||||
border: 5px solid #454545;
|
||||
animation: prixClipFix 2s linear infinite ;
|
||||
}
|
||||
.loader::after{
|
||||
border-color: #FF3D00;
|
||||
animation: prixClipFix 2s linear infinite , rotate 0.5s linear infinite reverse;
|
||||
inset: 6px;
|
||||
}
|
||||
@keyframes rotate {
|
||||
0% {transform: rotate(0deg)}
|
||||
100% {transform: rotate(360deg)}
|
||||
}
|
||||
@keyframes prixClipFix {
|
||||
0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)}
|
||||
25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)}
|
||||
50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)}
|
||||
75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)}
|
||||
100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)}
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block lg:hidden">
|
||||
@include('portal.ninja2020.components.html-viewer')
|
||||
</div>
|
||||
</div>
|
@ -53,32 +53,6 @@
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" @click.away="open = false"
|
||||
class="relative inline-block text-left">
|
||||
<div>
|
||||
<button @click="open = !open"
|
||||
class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:enter-end="transform opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white ring-1 ring-black ring-opacity-5">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="{{ $fullscreen_url ?? '?mode=fullscreen' }}"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user