diff --git a/resources/js/share-downloader.js b/resources/js/share-downloader.js index a496a34..88012a7 100644 --- a/resources/js/share-downloader.js +++ b/resources/js/share-downloader.js @@ -68,9 +68,11 @@ document.addEventListener('alpine:init', () => { /** How many of the run's files are in the folder. */ saved: 0, - /** The file on its way. */ + /** The file on its way: its path for the status line, its id for its row in the list. */ current: null, + currentId: null, + /** The files that failed, kept for the Retry button. */ failed: [], @@ -140,6 +142,7 @@ document.addEventListener('alpine:init', () => { for (const file of list) { const before = this.received this.current = file.path + this.currentId = file.id try { await this.save(file, controller.signal) @@ -162,6 +165,7 @@ document.addEventListener('alpine:init', () => { this.running = false this.current = null + this.currentId = null this.finished = ! controller.signal.aborted }, diff --git a/resources/views/livewire/share-download.blade.php b/resources/views/livewire/share-download.blade.php index fdc1909..f36dede 100644 --- a/resources/views/livewire/share-download.blade.php +++ b/resources/views/livewire/share-download.blade.php @@ -105,7 +105,16 @@ {{ Number::fileSize($file->file_size) }} - + {{-- The file the folder download is on; its row, always shown, is brought into view in the list as it starts. --}} + + + + diff --git a/tests/Browser/ShareFlowTest.php b/tests/Browser/ShareFlowTest.php index 3e31a9e..a57a82f 100644 --- a/tests/Browser/ShareFlowTest.php +++ b/tests/Browser/ShareFlowTest.php @@ -313,3 +313,40 @@ test('a long file list scrolls on its own and keeps the download buttons on the ->assertScript("[...document.querySelectorAll('[data-md-button]')].filter((button) => button.offsetParent !== null && button.textContent.trim().toLowerCase().startsWith('download all')).every((button) => button.getBoundingClientRect().bottom <= window.innerHeight)") ->assertNoJavaScriptErrors(); }); + +test('the folder download marks the file it is on and scrolls the list to it', function () { + $share = app(ShareService::class)->createShare(collect(range(1, 40))->map(fn (int $number): array => [ + 'file' => UploadedFile::fake()->createWithContent("file-{$number}.txt", "file {$number}"), + 'relativePath' => null, + ])->all()); + $last = $share->files->last(); + + $page = ready(visit(route('share.download', $share, false))->resize(1280, 800)); + + // Every file but the last is already in the folder, and the last one's request never answers, + // so the run stays on it. + $page->script("(async () => { + const root = await navigator.storage.getDirectory(); + for await (const name of root.keys()) { await root.removeEntry(name, { recursive: true }); } + for (let number = 1; number < 40; number++) { + const writable = await (await root.getFileHandle(`file-\${number}.txt`, { create: true })).createWritable(); + await writable.write(`file \${number}`); + await writable.close(); + } + window.showDirectoryPicker = () => Promise.resolve(root); + window.fetch = () => new Promise(() => {}); + Alpine.\$data(document.querySelector('[data-test=download-to-folder]')).supported = true; + })()"); + + $page->click('[data-test="download-to-folder"]'); + + $page->assertScript("[...document.querySelectorAll('[data-test=file-downloading]')].filter((mark) => mark.offsetParent !== null).length === 1") + ->assertScript("(() => { + const mark = [...document.querySelectorAll('[data-test=file-downloading]')].find((mark) => mark.offsetParent !== null); + const row = mark.closest('[data-md-list-item]'); + const list = document.querySelector('[data-test=share-file-list]').getBoundingClientRect(); + const rect = row.getBoundingClientRect(); + return row.textContent.includes('{$last->original_name}') && rect.top >= list.top - 1 && rect.bottom <= list.bottom + 1; + })()") + ->assertNoJavaScriptErrors(); +});