|
| 1 | +<!DOCTYPE html> |
| 2 | +<html class="h-full bg-white"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"/> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| 6 | + <title>Screenshot Diff Report — <%= failed %> failures</title> |
| 7 | + <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script> |
| 8 | + <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script> |
| 9 | +</head> |
| 10 | + |
| 11 | +<body class="h-full overflow-hidden"> |
| 12 | +<div x-data> |
| 13 | + <div class="flex h-screen"> |
| 14 | + <div class="flex min-w-0 flex-1 flex-col overflow-hidden"> |
| 15 | + |
| 16 | + <!-- Summary Bar --> |
| 17 | + <div class="bg-gray-50 border-b px-6 py-3 flex items-center justify-between"> |
| 18 | + <h1 class="text-lg font-semibold text-gray-900">Screenshot Diff Report</h1> |
| 19 | + <div class="flex gap-4 text-sm"> |
| 20 | + <span class="text-gray-600">Total: <strong><%= total %></strong></span> |
| 21 | + <span class="text-red-600">Failed: <strong><%= failed %></strong></span> |
| 22 | + <span class="text-green-600">Passed: <strong><%= passed %></strong></span> |
| 23 | + <span class="text-gray-500">(<%= success_rate %>%)</span> |
| 24 | + </div> |
| 25 | + </div> |
| 26 | + |
| 27 | + <div class="relative z-0 flex flex-1 overflow-hidden"> |
| 28 | + |
| 29 | + <!-- Main Content --> |
| 30 | + <main class="relative z-0 flex-1 overflow-y-auto focus:outline-none sm:order-last p-6"> |
| 31 | + <div x-data="{items: $store.screenshots.items}"> |
| 32 | + <template x-for="(item, id) in items"> |
| 33 | + <article x-show="item.selected"> |
| 34 | + <h2 class="text-lg font-medium text-gray-900 mb-4" x-text="item.name"></h2> |
| 35 | + |
| 36 | + <div class="mb-4"> |
| 37 | + <button @click="$store.diff.toggle()" |
| 38 | + :class="$store.diff.on ? 'bg-red-100 text-red-700' : 'bg-white text-gray-700'" |
| 39 | + type="button" |
| 40 | + class="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"> |
| 41 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5"> |
| 42 | + <path d="M16.5 6a3 3 0 0 0-3-3H6a3 3 0 0 0-3 3v7.5a3 3 0 0 0 3 3v-6A4.5 4.5 0 0 1 10.5 6h6Z"/> |
| 43 | + <path d="M18 7.5a3 3 0 0 1 3 3V18a3 3 0 0 1-3 3h-7.5a3 3 0 0 1-3-3v-7.5a3 3 0 0 1 3-3H18Z"/> |
| 44 | + </svg> |
| 45 | + <span x-text="$store.diff.on ? 'Showing Diff' : 'Show Diff'"></span> |
| 46 | + </button> |
| 47 | + </div> |
| 48 | + |
| 49 | + <div class="grid grid-cols-2 gap-4"> |
| 50 | + <div> |
| 51 | + <p class="text-sm font-medium text-gray-500 mb-2">Baseline</p> |
| 52 | + <div class="bg-black rounded-lg border overflow-hidden"> |
| 53 | + <img class="w-full" :alt="'Baseline: ' + item.name" :src="item.original"> |
| 54 | + </div> |
| 55 | + </div> |
| 56 | + <div> |
| 57 | + <p class="text-sm font-medium text-gray-500 mb-2" x-text="$store.diff.on ? 'Diff' : 'Current'"></p> |
| 58 | + <div class="bg-black rounded-lg border overflow-hidden"> |
| 59 | + <img class="w-full" :alt="'Current: ' + item.name" :src="$store.screenshots.comparison(id)"> |
| 60 | + </div> |
| 61 | + </div> |
| 62 | + </div> |
| 63 | + </article> |
| 64 | + </template> |
| 65 | + </div> |
| 66 | + </main> |
| 67 | + |
| 68 | + <!-- Sidebar --> |
| 69 | + <aside class="hidden w-72 border-r border-gray-200 sm:order-first sm:flex sm:flex-col"> |
| 70 | + <div class="p-3"> |
| 71 | + <input type="text" |
| 72 | + x-model="$store.screenshots.search" |
| 73 | + placeholder="Search screenshots..." |
| 74 | + class="w-full rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"> |
| 75 | + </div> |
| 76 | + <nav class="flex-1 overflow-y-auto p-3" x-data="{items: $store.screenshots.items}"> |
| 77 | + <template x-for="(item, id) in items"> |
| 78 | + <div class="mb-2" x-show="item.name.toLowerCase().includes(($store.screenshots.search || '').toLowerCase())"> |
| 79 | + <button @click="$store.screenshots.select(id)" |
| 80 | + class="w-full text-left rounded-lg border-2 p-1 transition-colors" |
| 81 | + :class="item.selected ? 'border-indigo-500 shadow-md' : 'border-transparent hover:border-gray-300'"> |
| 82 | + <div class="aspect-w-5 aspect-h-3 overflow-hidden rounded bg-black"> |
| 83 | + <img class="object-contain" :alt="item.name" :src="item.diff || item.new"> |
| 84 | + </div> |
| 85 | + <p class="mt-1 truncate text-xs text-gray-600" x-text="item.name"></p> |
| 86 | + </button> |
| 87 | + </div> |
| 88 | + </template> |
| 89 | + </nav> |
| 90 | + </aside> |
| 91 | + </div> |
| 92 | + </div> |
| 93 | + </div> |
| 94 | +</div> |
| 95 | + |
| 96 | +<script> |
| 97 | + document.addEventListener('alpine:init', () => { |
| 98 | + Alpine.store('diff', { |
| 99 | + on: false, |
| 100 | + toggle() { this.on = !this.on } |
| 101 | + }) |
| 102 | + |
| 103 | + Alpine.store('screenshots', { |
| 104 | + current: 0, |
| 105 | + search: '', |
| 106 | + items: <%= failed_screenshots.to_json.gsub("</", "<\\/") %>, |
| 107 | + |
| 108 | + select(id) { |
| 109 | + if (this.items.length === 0) return |
| 110 | + if (this.items[this.current]) this.items[this.current].selected = false |
| 111 | + this.current = id |
| 112 | + this.items[id].selected = true |
| 113 | + }, |
| 114 | + |
| 115 | + comparison(id) { |
| 116 | + return Alpine.store('diff').on ? this.items[id].diff : this.items[id].new |
| 117 | + } |
| 118 | + }) |
| 119 | + |
| 120 | + const store = Alpine.store('screenshots') |
| 121 | + if (store.items.length > 0) store.select(0) |
| 122 | + }) |
| 123 | +</script> |
| 124 | +</body> |
| 125 | +</html> |
0 commit comments