Skip to content

Commit f68b1d0

Browse files
authored
feat(uploader): deferred uploads on create + grouped listAll view (#3)
* Added ability to use the uploader when creating. * feat(uploader): deferred uploads for create + grouped `list-all` view - Allow mounting with model (no id) and queue files during create flow - Attach queued files after save via `media:attach` event; emit `media-attached` - `uploadFiles()` queues & flashes when no target; `nextOrder()` derives from queue - Add `listAll` prop to show **all collections** grouped by collection name, still editable - Docs: add Tailwind dark-mode snippet, usage examples, events & props updates - Changelog/README: note new features and behavior; no breaking changes
1 parent 792f0d5 commit f68b1d0

File tree

5 files changed

+771
-302
lines changed

5 files changed

+771
-302
lines changed

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
## [Unreleased]
1212

1313
### Added
14-
14+
- **Deferred uploads on create**: You can now queue files before the target model exists and attach them after save by dispatching the `media:attach` event. The uploader accepts `model="post"` (no `id`) on create screens, holds the queue + per-file meta, and attaches once you dispatch. Emits `media-attached` when done.
15+
- **`list-all` view**: Set `:list-all="true"` to show **all collections** for the current model in a single list, grouped by collection name. Items remain fully editable (caption/description/order).
16+
- **Tailwind dark mode docs/snippets**: Added guidance and examples for enabling global light/dark mode with the Tailwind theme.
1517

1618
### Changed
19+
- Graceful “no target yet” behavior on create screens:
20+
- `nextOrder()` now derives order from the local queue if the model isn’t saved yet.
21+
- `uploadFiles()` **queues** when there’s no target (instead of erroring) and flashes: _“Files queued. They will be attached after you save.”_
1722

1823
### Fixed
19-
24+
- N/A
2025

2126
---
2227
## [v0.2.0] — 2025-09-01
@@ -95,5 +100,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
95100

96101
---
97102

98-
[Unreleased]: https://github.com/codebyray/livewire-media-uploader/compare/v0.1.0...HEAD
103+
[Unreleased]: https://github.com/codebyray/livewire-media-uploader/compare/v0.2.0...HEAD
99104
[v0.1.0]: https://github.com/codebyray/livewire-media-uploader/releases/tag/v0.1.0

README.md

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ Livewire Media Uploader is a reusable Livewire v3 component that integrates seam
1010
- [Requirements](#requirements)
1111
- [Installation](#installation)
1212
- [Publishing Assets](#publishing-assets)
13+
- [Theme System](#theme-system-tailwind--bootstrap--custom)
14+
- [Dark Mode - Tailwind](#dark-mode-tailwind-theme)
15+
- [Custom Theme](#custom-themes)
1316
- [Quick Start](#quick-start)
1417
- [Usage Examples](#usage-examples)
18+
- [Create flow (deferred uploads)](#create-flow-deferred-uploads)
1519
- [Configuration](#configuration)
1620
- [Props](#props)
1721
- [Events](#events)
@@ -112,6 +116,19 @@ return [
112116
// ...
113117
];
114118
```
119+
### Dark mode (Tailwind theme)
120+
This package’s Tailwind theme is dark-ready. Add this tiny snippet in your main layout `<head>` to apply the user’s saved choice / system default:
121+
122+
```html
123+
<script>
124+
(() => {
125+
const t = localStorage.theme ?? 'system';
126+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
127+
const dark = t === 'dark' || (t === 'system' && prefersDark);
128+
if (dark) document.documentElement.classList.add('dark');
129+
})();
130+
</script>
131+
```
115132
### Custom themes
116133
- Copy an existing theme directory (e.g. themes/tailwind) to themes/custom and edit the Blade.
117134
- Register it in the map and select it:
@@ -293,7 +310,75 @@ MEDIA_MAXKB_DEFAULT=10240
293310
:maxSizeKb="5120"
294311
/>
295312
```
313+
### Create flow (deferred uploads)
314+
315+
You can let users pick files **before** the model exists, and attach them **after** save.
316+
317+
**Blade (create page)**
318+
```html
319+
<!-- Note: pass model class/alias without id -->
320+
<livewire:media-uploader
321+
model="post"
322+
collection="images"
323+
preset="images"
324+
:multiple="true"
325+
:showList="true"
326+
/>
327+
```
328+
#### Livewire component (simplified)
329+
```php
330+
use App\Models\Post;
331+
use Illuminate\Support\Facades\Auth;
332+
use Livewire\Attributes\On;
333+
use Livewire\Component;
334+
335+
class PostCreate extends Component
336+
{
337+
public string $title = '';
338+
public string $body = '';
339+
public ?int $pendingPostId = null;
340+
341+
protected function rules(): array
342+
{
343+
return ['title' => 'required|string|max:255', 'body' => 'required|string'];
344+
}
345+
346+
public function save(): void
347+
{
348+
$post = Post::create([
349+
'user_id' => Auth::id(),
350+
'title' => $this->title,
351+
'body' => $this->body,
352+
]);
353+
354+
// Let uploaders attach everything queued for this collection
355+
$this->pendingPostId = $post->id;
296356
357+
// Fire once per collection rendered on the page
358+
$this->dispatch('media:attach', model: 'post', id: $post->id, collection: 'images');
359+
}
360+
361+
#[On('media-attached')]
362+
public function afterMediaAttached(string $model, string|int $id): void
363+
{
364+
if ($this->pendingPostId && (int)$id === (int)$this->pendingPostId) {
365+
$this->pendingPostId = null;
366+
$this->redirectRoute('posts.show', ['post' => $id], navigate: true);
367+
}
368+
}
369+
370+
public function render() { return view('livewire.posts.post-create'); }
371+
}
372+
```
373+
374+
#### How it works
375+
- On create screens, the component accepts model="post" without an id.
376+
- Files and per-file metadata are queued locally.
377+
- After you persist the model, dispatch:
378+
```php
379+
$this->dispatch('media:attach', model: 'post', id: $post->id, collection: 'images');
380+
```
381+
- The uploader resolves the saved target, attaches any queued files, and emits media-attached.
297382
---
298383
299384
## Configuration
@@ -314,7 +399,16 @@ Example:
314399
'attachments' => 'docs',
315400
],
316401
```
402+
Show all collections together (grouped)
403+
Set `:list-all="true"` to render a grouped list of **every collection** on the target model. Items stay fully editable.
317404
405+
```html
406+
<livewire:media-uploader
407+
:for="$post"
408+
:list-all="true"
409+
:showList="true"
410+
/>
411+
```
318412
The component decides the active preset in this order:
319413
1. Explicit `$preset` prop
320414
2. Mapping from `collections`
@@ -343,16 +437,19 @@ The component decides the active preset in this order:
343437
| `namespaces` | `array` | `['App\\Models']` | Namespaces for dotted-path resolution. |
344438
| `aliases` | `array` | `[]` | Local alias map, e.g. `['profile' => \App\Models\User::class]`. |
345439
| `attachedFilesTitle` | `string` | `"Current gallery"` | Heading text in the list card. |
440+
| `listAll` | `bool` | `false` | When `true`, the attached media list shows **all collections**, grouped by collection name (still editable). |
346441
347442
---
348443
349444
## Events
350445
351446
The component dispatches browser events you can listen for:
352447
353-
- `media-uploaded` — after an upload completes
354-
- `media-deleted` — after a deletion (`detail.id` contains the Media ID)
355-
- `media-meta-updated` — after saving inline metadata
448+
- `media:attach` — **incoming** event the component listens for. Arguments: `model` (class/alias), `id`, optional `collection`, optional `disk`. Triggers attaching of any queued files to the now-saved target.
449+
- `media-attached` — emitted after a successful `media:attach`. Payload: `{ model: FQCN, id: string }`.
450+
- `media-uploaded` — emitted after an immediate upload (when a target already exists).
451+
- `media-deleted` — emitted after deletion (`detail.id` contains the Media ID).
452+
- `media-meta-updated` — emitted after inline metadata is saved.
356453
357454
Example:
358455
```html

0 commit comments

Comments
 (0)