From 57ab16675ec55e50c9e6d4e7a76bf4c59fe2f241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Fri, 9 Apr 2021 18:47:19 +0200 Subject: [PATCH] livewire thread --- app/Console/Commands/GenerateHtml.php | 46 ++++++++++++++----- app/Models/Thread.php | 5 ++ content/threads/1-rt-1-tip.md | 3 +- content/threads/laravel-clean-code-tactics.md | 6 ++- content/threads/obscure-livewire-tips.md | 15 ++++++ content/tips/advanced-notifications.md | 15 ++++++ content/tips/advanced-trait-design.md | 16 +++++++ content/tips/call-methods-from-javascript.md | 14 ++++++ .../tips/compose-components-using-traits.md | 16 +++++++ content/tips/custom-response-effects.md | 14 ++++++ content/tips/livewire-readonly-properties.md | 18 ++++++++ .../replace-state-with-a-custom-directive.md | 14 ++++++ content/tips/share-state-using-sprucewire.md | 15 ++++++ .../use-wire-entangle-instead-of-entangle.md | 14 ++++++ content/tips/use-wire-replace.md | 14 ++++++ .../views/components/author-card.blade.php | 36 +++++++++++---- resources/views/threads/show.blade.php | 4 +- routes/web.php | 4 ++ 18 files changed, 244 insertions(+), 25 deletions(-) create mode 100644 content/threads/obscure-livewire-tips.md create mode 100644 content/tips/advanced-notifications.md create mode 100644 content/tips/advanced-trait-design.md create mode 100644 content/tips/call-methods-from-javascript.md create mode 100644 content/tips/compose-components-using-traits.md create mode 100644 content/tips/custom-response-effects.md create mode 100644 content/tips/livewire-readonly-properties.md create mode 100644 content/tips/replace-state-with-a-custom-directive.md create mode 100644 content/tips/share-state-using-sprucewire.md create mode 100644 content/tips/use-wire-entangle-instead-of-entangle.md create mode 100644 content/tips/use-wire-replace.md diff --git a/app/Console/Commands/GenerateHtml.php b/app/Console/Commands/GenerateHtml.php index 03d3861..1cd9f02 100644 --- a/app/Console/Commands/GenerateHtml.php +++ b/app/Console/Commands/GenerateHtml.php @@ -4,12 +4,14 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Contracts\Support\Renderable; +use Illuminate\Http\Response; use Illuminate\Routing\RedirectController; use Illuminate\Routing\Route; use Illuminate\Routing\Router; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; +use Symfony\Component\HttpFoundation\RedirectResponse; class GenerateHtml extends Command { @@ -44,30 +46,44 @@ class GenerateHtml extends Command ->values() ->each(function (Route $route) use ($outdir) { 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())) { 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 { - $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) { if (! is_dir($path = $root . $instance->getRouteKey())) { 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) { + if ($response instanceof RedirectResponse) { + // Redirect responses shouldn't be rendered with headers + return $response->getContent(); + } + if ($response instanceof Renderable) { + // Views should be rendered using render() return $response->render(); } diff --git a/app/Models/Thread.php b/app/Models/Thread.php index f15e28c..a54a43e 100644 --- a/app/Models/Thread.php +++ b/app/Models/Thread.php @@ -15,6 +15,10 @@ class Thread extends Model public $timestamps = false; + public $casts = [ + 'links' => 'array', + ]; + public static function schema(Blueprint $table) { $table->string('slug')->unique(); @@ -22,6 +26,7 @@ class Thread extends Model $table->string('tweet_id')->nullable(); $table->foreignId('author_username')->constrained('authors', 'username'); $table->text('content'); + $table->json('links')->default('{}'); $table->timestamp('created_at'); } diff --git a/content/threads/1-rt-1-tip.md b/content/threads/1-rt-1-tip.md index 1e73ac5..7326dd6 100644 --- a/content/threads/1-rt-1-tip.md +++ b/content/threads/1-rt-1-tip.md @@ -4,5 +4,6 @@ title: '1 RT = 1 tip' tweet_id: 1308082888324374528 author_username: samuelstancl created_at: 2021-04-07T18:16:23+00:00 +links: {} --- -A thread of misc tips, originally one tip per one retweet. \ No newline at end of file +A thread of misc tips, originally one tip per one retweet. diff --git a/content/threads/laravel-clean-code-tactics.md b/content/threads/laravel-clean-code-tactics.md index 92b8c38..7fcfa68 100644 --- a/content/threads/laravel-clean-code-tactics.md +++ b/content/threads/laravel-clean-code-tactics.md @@ -4,5 +4,9 @@ title: 'Laravel Clean Code Tactics' tweet_id: 1272822437181378561 author_username: samuelstancl 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). \ No newline at end of file +The OG thread. Get printable PDF versions [here](https://gum.co/laravel-clean-code). diff --git a/content/threads/obscure-livewire-tips.md b/content/threads/obscure-livewire-tips.md new file mode 100644 index 0000000..cf23749 --- /dev/null +++ b/content/threads/obscure-livewire-tips.md @@ -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). diff --git a/content/tips/advanced-notifications.md b/content/tips/advanced-notifications.md new file mode 100644 index 0000000..2fe3c62 --- /dev/null +++ b/content/tips/advanced-notifications.md @@ -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 diff --git a/content/tips/advanced-trait-design.md b/content/tips/advanced-trait-design.md new file mode 100644 index 0000000..207fc53 --- /dev/null +++ b/content/tips/advanced-trait-design.md @@ -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 diff --git a/content/tips/call-methods-from-javascript.md b/content/tips/call-methods-from-javascript.md new file mode 100644 index 0000000..7c709cd --- /dev/null +++ b/content/tips/call-methods-from-javascript.md @@ -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! diff --git a/content/tips/compose-components-using-traits.md b/content/tips/compose-components-using-traits.md new file mode 100644 index 0000000..f1f508b --- /dev/null +++ b/content/tips/compose-components-using-traits.md @@ -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 diff --git a/content/tips/custom-response-effects.md b/content/tips/custom-response-effects.md new file mode 100644 index 0000000..fa0a8bb --- /dev/null +++ b/content/tips/custom-response-effects.md @@ -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 diff --git a/content/tips/livewire-readonly-properties.md b/content/tips/livewire-readonly-properties.md new file mode 100644 index 0000000..4a1a675 --- /dev/null +++ b/content/tips/livewire-readonly-properties.md @@ -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! diff --git a/content/tips/replace-state-with-a-custom-directive.md b/content/tips/replace-state-with-a-custom-directive.md new file mode 100644 index 0000000..82359d6 --- /dev/null +++ b/content/tips/replace-state-with-a-custom-directive.md @@ -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. diff --git a/content/tips/share-state-using-sprucewire.md b/content/tips/share-state-using-sprucewire.md new file mode 100644 index 0000000..e46b2bf --- /dev/null +++ b/content/tips/share-state-using-sprucewire.md @@ -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 diff --git a/content/tips/use-wire-entangle-instead-of-entangle.md b/content/tips/use-wire-entangle-instead-of-entangle.md new file mode 100644 index 0000000..172c12e --- /dev/null +++ b/content/tips/use-wire-entangle-instead-of-entangle.md @@ -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. diff --git a/content/tips/use-wire-replace.md b/content/tips/use-wire-replace.md new file mode 100644 index 0000000..1c9c94d --- /dev/null +++ b/content/tips/use-wire-replace.md @@ -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. diff --git a/resources/views/components/author-card.blade.php b/resources/views/components/author-card.blade.php index 6e83c8d..f3a3d41 100644 --- a/resources/views/components/author-card.blade.php +++ b/resources/views/components/author-card.blade.php @@ -1,4 +1,7 @@ -@props(['tip']) +@props([ + 'tip', + 'links' => [], +])
@@ -23,15 +26,30 @@
@endif -
-
Tweet
+ @if($tip->tweet_id) +
+
Tweet
+
+ + twitter.com/... + +
+
+ @endif -
- - twitter.com/... - -
-
+ @if($links) +
+
Links
+ +
+ @foreach($links as $link) + + {{ ucfirst($link['name']) }} + + @endforeach +
+
+ @endif diff --git a/resources/views/threads/show.blade.php b/resources/views/threads/show.blade.php index 0350fe4..cf95d06 100644 --- a/resources/views/threads/show.blade.php +++ b/resources/views/threads/show.blade.php @@ -1,4 +1,4 @@ - +
@@ -24,7 +24,7 @@
- +
diff --git a/routes/web.php b/routes/web.php index f37984c..71fd473 100644 --- a/routes/web.php +++ b/routes/web.php @@ -20,6 +20,10 @@ Route::middleware('static')->group(function () { return view('tips.index', ['tips' => Tip::all()]); })->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) { return view('threads.show', [ 'thread' => $thread,