mirror of
https://github.com/archtechx/laravel-tips.git
synced 2025-12-12 05:14:04 +00:00
livewire thread
This commit is contained in:
parent
a60370fbc9
commit
57ab16675e
18 changed files with 244 additions and 25 deletions
|
|
@ -4,12 +4,14 @@ namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Support\Renderable;
|
use Illuminate\Contracts\Support\Renderable;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Routing\RedirectController;
|
use Illuminate\Routing\RedirectController;
|
||||||
use Illuminate\Routing\Route;
|
use Illuminate\Routing\Route;
|
||||||
use Illuminate\Routing\Router;
|
use Illuminate\Routing\Router;
|
||||||
use Illuminate\Routing\UrlGenerator;
|
use Illuminate\Routing\UrlGenerator;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
|
||||||
class GenerateHtml extends Command
|
class GenerateHtml extends Command
|
||||||
{
|
{
|
||||||
|
|
@ -44,30 +46,44 @@ class GenerateHtml extends Command
|
||||||
->values()
|
->values()
|
||||||
->each(function (Route $route) use ($outdir) {
|
->each(function (Route $route) use ($outdir) {
|
||||||
if (! count($route->parameterNames())) {
|
if (! count($route->parameterNames())) {
|
||||||
if ($route->getActionName() === '\\' . RedirectController::class) {
|
|
||||||
// $action = $this->router->match('get', $route->defaults['destination'])->getAction();
|
|
||||||
// Redirects don't work yet
|
|
||||||
$action = $route->getAction();
|
|
||||||
} else {
|
|
||||||
$action = $route->getAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_dir($path = $outdir . '/' . $route->uri())) {
|
if (! is_dir($path = $outdir . '/' . $route->uri())) {
|
||||||
mkdir($path, 0777, true);
|
mkdir($path, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
File::put($outdir . '/' . $route->uri() . '/index.html', $this->render($action['uses']()));
|
File::put($outdir . '/' . $route->uri() . '/index.html', $this->render($route->getAction()['uses']()));
|
||||||
} else {
|
} else {
|
||||||
$model = 'App\\Models\\' . ucfirst($parameter = $route->parameterNames()[0]);
|
$model = 'App\\Models\\' . ucfirst($firstParameter = $route->parameterNames()[0]);
|
||||||
|
|
||||||
$root = (string) Str::of($outdir . '/' . $route->uri())->before("{{$parameter}}");
|
$root = (string) Str::of($outdir . '/' . $route->uri())->before("{{$firstParameter}}");
|
||||||
|
|
||||||
foreach ($model::cursor() as $instance) {
|
foreach ($model::cursor() as $instance) {
|
||||||
if (! is_dir($path = $root . $instance->getRouteKey())) {
|
if (! is_dir($path = $root . $instance->getRouteKey())) {
|
||||||
mkdir($path, 0777, true);
|
mkdir($path, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
File::put($path . '/index.html', $this->render($route->getAction()['uses']($instance)));
|
if (count($route->parameterNames()) === 1) {
|
||||||
|
File::put($path . '/index.html', $this->render($route->getAction()['uses']($instance)));
|
||||||
|
} else { // 2 parameters
|
||||||
|
$datasets = [];
|
||||||
|
|
||||||
|
foreach ($route->parameterNames() as $parameter) {
|
||||||
|
if ($parameter === $firstParameter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($instance->{Str::plural($parameter)} as $key => $value) {
|
||||||
|
$datasets[$key] = [$instance, $key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($datasets as $key => $data) {
|
||||||
|
if (! is_dir($path = $path . '/' . $key)) {
|
||||||
|
mkdir($path, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
File::put($path . '/index.html', $this->render($route->getAction()['uses'](...$data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -76,7 +92,13 @@ class GenerateHtml extends Command
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function render ($response) {
|
protected function render ($response) {
|
||||||
|
if ($response instanceof RedirectResponse) {
|
||||||
|
// Redirect responses shouldn't be rendered with headers
|
||||||
|
return $response->getContent();
|
||||||
|
}
|
||||||
|
|
||||||
if ($response instanceof Renderable) {
|
if ($response instanceof Renderable) {
|
||||||
|
// Views should be rendered using render()
|
||||||
return $response->render();
|
return $response->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@ class Thread extends Model
|
||||||
|
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
|
public $casts = [
|
||||||
|
'links' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
public static function schema(Blueprint $table)
|
public static function schema(Blueprint $table)
|
||||||
{
|
{
|
||||||
$table->string('slug')->unique();
|
$table->string('slug')->unique();
|
||||||
|
|
@ -22,6 +26,7 @@ class Thread extends Model
|
||||||
$table->string('tweet_id')->nullable();
|
$table->string('tweet_id')->nullable();
|
||||||
$table->foreignId('author_username')->constrained('authors', 'username');
|
$table->foreignId('author_username')->constrained('authors', 'username');
|
||||||
$table->text('content');
|
$table->text('content');
|
||||||
|
$table->json('links')->default('{}');
|
||||||
$table->timestamp('created_at');
|
$table->timestamp('created_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@ title: '1 RT = 1 tip'
|
||||||
tweet_id: 1308082888324374528
|
tweet_id: 1308082888324374528
|
||||||
author_username: samuelstancl
|
author_username: samuelstancl
|
||||||
created_at: 2021-04-07T18:16:23+00:00
|
created_at: 2021-04-07T18:16:23+00:00
|
||||||
|
links: {}
|
||||||
---
|
---
|
||||||
A thread of misc tips, originally one tip per one retweet.
|
A thread of misc tips, originally one tip per one retweet.
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,9 @@ title: 'Laravel Clean Code Tactics'
|
||||||
tweet_id: 1272822437181378561
|
tweet_id: 1272822437181378561
|
||||||
author_username: samuelstancl
|
author_username: samuelstancl
|
||||||
created_at: 2021-04-07T18:16:07+00:00
|
created_at: 2021-04-07T18:16:07+00:00
|
||||||
|
links:
|
||||||
|
download:
|
||||||
|
name: Printable versions
|
||||||
|
url: https://gum.co/laravel-clean-code
|
||||||
---
|
---
|
||||||
The OG thread. Get printable PDF versions [here](https://gum.co/laravel-clean-code).
|
The OG thread. Get printable PDF versions [here](https://gum.co/laravel-clean-code).
|
||||||
|
|
|
||||||
15
content/threads/obscure-livewire-tips.md
Normal file
15
content/threads/obscure-livewire-tips.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
slug: obscure-livewire-tips
|
||||||
|
title: 'Obscure Livewire Tips'
|
||||||
|
tweet_id: 1380538340982788096
|
||||||
|
author_username: samuelstancl
|
||||||
|
created_at: 2021-04-09T15:19:23+02:00
|
||||||
|
links:
|
||||||
|
download:
|
||||||
|
name: Download code
|
||||||
|
url: https://gum.co/livewire-tips
|
||||||
|
---
|
||||||
|
|
||||||
|
Little-known Livewire tips.
|
||||||
|
|
||||||
|
Get the supporting resources, code examples, and Blade directives [here](https://gum.co/livewire-tips).
|
||||||
15
content/tips/advanced-notifications.md
Normal file
15
content/tips/advanced-notifications.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: 'Advanced notifications'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/AAMo0d/download'
|
||||||
|
created_at: 2021-04-09T17:24:56+02:00
|
||||||
|
slug: advanced-notifications
|
||||||
|
---
|
||||||
|
|
||||||
|
This trait allows dispatching notifications:
|
||||||
|
- on the current page
|
||||||
|
- on the next page (after redirect)
|
||||||
|
- from any part of your code: you can run Lean::notify() in an action class, a model method, or anywhere else — and it will be sent to the browser
|
||||||
16
content/tips/advanced-trait-design.md
Normal file
16
content/tips/advanced-trait-design.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
title: 'Advanced trait design'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/YjzF2w/download'
|
||||||
|
created_at: 2021-04-09T17:23:56+02:00
|
||||||
|
slug: advanced-trait-design
|
||||||
|
---
|
||||||
|
|
||||||
|
Let's look at the same trait again. It does two interesting things.
|
||||||
|
|
||||||
|
1. Livewire::listen() is superior to hydrate* and dehydrate* when you need access to specific parts of the lifecycle
|
||||||
|
|
||||||
|
2. The `instanceof self` check scopes the listener to the component
|
||||||
14
content/tips/call-methods-from-javascript.md
Normal file
14
content/tips/call-methods-from-javascript.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: 'Call methods from JavaScript'
|
||||||
|
tweet_id: '1380545900020436996'
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://pbs.twimg.com/media/EyivLICXIAEX-40.jpg'
|
||||||
|
created_at: 2021-04-09T17:11:56+02:00
|
||||||
|
slug: call-methods-from-javascript
|
||||||
|
---
|
||||||
|
|
||||||
|
Livewire's properties are extremely powerful, which often makes us forget that we can also call methods from the frontend.
|
||||||
|
|
||||||
|
Livewire gives you a full component API. Use it!
|
||||||
16
content/tips/compose-components-using-traits.md
Normal file
16
content/tips/compose-components-using-traits.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
title: 'Compose components using traits'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/j3Jtnw/download'
|
||||||
|
created_at: 2021-04-09T17:12:56+02:00
|
||||||
|
slug: compose-components-using-traits
|
||||||
|
---
|
||||||
|
|
||||||
|
Traits are a powerful way to reuse functionality between Livewire components.
|
||||||
|
|
||||||
|
They're generally better than component nesting, since that comes with more complexity and worse performance.
|
||||||
|
|
||||||
|
Next up: Advanced trait examples
|
||||||
14
content/tips/custom-response-effects.md
Normal file
14
content/tips/custom-response-effects.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: 'Custom response effects'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/YjzF2w/download'
|
||||||
|
created_at: 2021-04-09T17:22:56+02:00
|
||||||
|
slug: custom-response-effects
|
||||||
|
---
|
||||||
|
|
||||||
|
You can add custom data to response effects. Those are separate from component data, and act more like events.
|
||||||
|
|
||||||
|
You can use them when you have some JS code that looks at Livewire responses, and when you want more control than dispatching browser events
|
||||||
18
content/tips/livewire-readonly-properties.md
Normal file
18
content/tips/livewire-readonly-properties.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
title: 'Readonly properties'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/oG5FqU/download'
|
||||||
|
created_at: 2021-04-09T17:28:56+02:00
|
||||||
|
slug: livewire-readonly-properties
|
||||||
|
---
|
||||||
|
|
||||||
|
They're a great way to make your components safer and faster.
|
||||||
|
|
||||||
|
Say you have a component for editing some resource.
|
||||||
|
|
||||||
|
You enforce the ACL in mount().
|
||||||
|
|
||||||
|
But if the user could change the currently edited resource, you'd need to enforce ACL on *all* requests!
|
||||||
14
content/tips/replace-state-with-a-custom-directive.md
Normal file
14
content/tips/replace-state-with-a-custom-directive.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: 'Replace children with a custom directive'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/w6vdHe/download'
|
||||||
|
created_at: 2021-04-09T17:27:56+02:00
|
||||||
|
slug: replace-children-with-a-custom-directive
|
||||||
|
---
|
||||||
|
|
||||||
|
Imagine that you have a nested component that depends on the parent state. You don't need to sync child state into parent, you just want to replace the child when the parent state changes.
|
||||||
|
|
||||||
|
You can use my custom directive for that.
|
||||||
15
content/tips/share-state-using-sprucewire.md
Normal file
15
content/tips/share-state-using-sprucewire.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
title: 'Share state with Sprucewire'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images: {}
|
||||||
|
created_at: 2021-04-09T17:26:56+02:00
|
||||||
|
slug: share-state-with-sprucewire
|
||||||
|
---
|
||||||
|
|
||||||
|
If you have two components that share some state, and you'd like to keep them in sync in a more effective way than dispatching listeners back and forth, check out Sprucewire.
|
||||||
|
|
||||||
|
It works almost like Vuex for Livewire.
|
||||||
|
|
||||||
|
https://twitter.com/_joshhanley/status/1362646916333334528
|
||||||
14
content/tips/use-wire-entangle-instead-of-entangle.md
Normal file
14
content/tips/use-wire-entangle-instead-of-entangle.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: 'Quick tip: Use $wire.entangle() instead of @entangle'
|
||||||
|
tweet_id: ''
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://cln.sh/tjz7SC/download'
|
||||||
|
created_at: 2021-04-09T17:25:56+02:00
|
||||||
|
slug: use-wire-entangle-instead-of-entangle
|
||||||
|
---
|
||||||
|
|
||||||
|
It's good to get into the habit of using $wire, since it provides a full communication layer for your Livewire component.
|
||||||
|
|
||||||
|
It also doesn't run into issues when you use it inside single quoted attributes.
|
||||||
14
content/tips/use-wire-replace.md
Normal file
14
content/tips/use-wire-replace.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
title: 'Use wire:replace'
|
||||||
|
tweet_id: '1380542122105921536'
|
||||||
|
thread_slug: obscure-livewire-tips
|
||||||
|
author_username: samuelstancl
|
||||||
|
images:
|
||||||
|
- 'https://pbs.twimg.com/media/EyirvPAWQAIN6ib.jpg'
|
||||||
|
created_at: 2021-04-09T16:11:56+02:00
|
||||||
|
slug: use-wire-replace
|
||||||
|
---
|
||||||
|
|
||||||
|
As you know, Livewire sometimes runs into painful DOM diffing issues. They can usually be solved with simple workarounds, but sometimes those workarounds get quite complex.
|
||||||
|
|
||||||
|
So I built a package that adds a `wire:replace` directive, telling Livewire to replace an entire chunk of the DOM, instead of trying to diff individual changes.
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
@props(['tip'])
|
@props([
|
||||||
|
'tip',
|
||||||
|
'links' => [],
|
||||||
|
])
|
||||||
|
|
||||||
<div class="flex w-full p-6 space-x-6 bg-gray-50 rounded-2xl">
|
<div class="flex w-full p-6 space-x-6 bg-gray-50 rounded-2xl">
|
||||||
<a href="{{ $tip->author->profile_url }}" target="_blank">
|
<a href="{{ $tip->author->profile_url }}" target="_blank">
|
||||||
|
|
@ -23,15 +26,30 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div>
|
@if($tip->tweet_id)
|
||||||
<dt class="text-sm text-gray-500">Tweet</dt>
|
<div>
|
||||||
|
<dt class="text-sm text-gray-500">Tweet</dt>
|
||||||
|
<dd>
|
||||||
|
<x-link href="{{ $tip->tweet_url }}" target="_blank">
|
||||||
|
twitter.com/...
|
||||||
|
</x-link>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<dd>
|
@if($links)
|
||||||
<x-link href="{{ $tip->tweet_url }}" target="_blank">
|
<div>
|
||||||
twitter.com/...
|
<dt class="text-sm text-gray-500">Links</dt>
|
||||||
</x-link>
|
|
||||||
</dd>
|
<dd>
|
||||||
</div>
|
@foreach($links as $link)
|
||||||
|
<x-link href="{{ $link['url'] }}" target="_blank">
|
||||||
|
{{ ucfirst($link['name']) }}
|
||||||
|
</x-link>
|
||||||
|
@endforeach
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</dl>
|
</dl>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<x:layout :title="$thread->title">
|
<x:layout :title="$thread->title" :preview="$thread->title">
|
||||||
<header class="relative py-24">
|
<header class="relative py-24">
|
||||||
<div
|
<div
|
||||||
class="absolute w-full transform skew-y-12 pointer-events-none h-72 md:h-96 -translate-y-1/4 bg-gradient-to-br from-yellow-300 to-pink-400 mix-blend-multiply">
|
class="absolute w-full transform skew-y-12 pointer-events-none h-72 md:h-96 -translate-y-1/4 bg-gradient-to-br from-yellow-300 to-pink-400 mix-blend-multiply">
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<section class="py-12 bg-white md:py-24">
|
<section class="py-12 bg-white md:py-24">
|
||||||
<x:container>
|
<x:container>
|
||||||
<x:author-card :tip="$thread" />
|
<x:author-card :tip="$thread" :links="$thread->links" />
|
||||||
</x:container>
|
</x:container>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ Route::middleware('static')->group(function () {
|
||||||
return view('tips.index', ['tips' => Tip::all()]);
|
return view('tips.index', ['tips' => Tip::all()]);
|
||||||
})->name('tip.index');
|
})->name('tip.index');
|
||||||
|
|
||||||
|
Route::get('/threads/{thread}/{link}', function (Thread $thread, string $link) {
|
||||||
|
return redirect($thread->links[$link]['url']);
|
||||||
|
})->name('thread.show');
|
||||||
|
|
||||||
Route::get('/threads/{thread}', function (Thread $thread) {
|
Route::get('/threads/{thread}', function (Thread $thread) {
|
||||||
return view('threads.show', [
|
return view('threads.show', [
|
||||||
'thread' => $thread,
|
'thread' => $thread,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue