@percy/report 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,525 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Percy Report</title>
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
8
+ <style>
9
+ :root {
10
+ --background-color: #fefefe;
11
+ --color-highlight-light: #f9f9f9;
12
+ --color-faint: #fafafa;
13
+ --color-secondary: #9c9ca1;
14
+ --color-primary: rgb(130, 69, 165);
15
+ --border-color: rgba(0, 0, 0, 0.1);
16
+ --color-backdrop: rgba(0, 0, 0, 0.7);
17
+ --color-diff: rgb(255, 0, 0, 0.2);
18
+ }
19
+
20
+ body {
21
+ background-color: var(--background-color);
22
+ margin: 0;
23
+ padding: 0;
24
+ font-size: 14px;
25
+ }
26
+
27
+ nav {
28
+ z-index: 999;
29
+ }
30
+
31
+ input[type=text] {
32
+ border: 1px solid var(--border-color);
33
+ padding: 5px;
34
+ border-radius: 10px;
35
+ }
36
+
37
+ input[type=range] {
38
+ -webkit-appearance: none;
39
+ /* Override default CSS styles */
40
+ appearance: none;
41
+ width: 100%;
42
+ height: 10px;
43
+ background: var(--color-highlight-light);
44
+ outline: none;
45
+ border-radius: 20px;
46
+ }
47
+
48
+ input[type=range]:hover {
49
+ opacity: 1;
50
+ }
51
+
52
+ input[type=range]::-webkit-slider-thumb {
53
+ -webkit-appearance: none;
54
+ appearance: none;
55
+ background-color: var(--color-primary);
56
+ width: 15px;
57
+ height: 15px;
58
+ border-radius: 50%;
59
+ }
60
+
61
+ input[type=range]::-moz-range-thumb {
62
+ background-color: var(--color-primary);
63
+ width: 15px;
64
+ height: 15px;
65
+ border-radius: 50%;
66
+ }
67
+
68
+ input[type=range]::before {
69
+ display: block;
70
+ position: absolute;
71
+ left: 0;
72
+ width: 80%;
73
+ height: 100%;
74
+ background-color: var(--color-primary);
75
+ }
76
+
77
+ .app {
78
+ display: flex;
79
+ flex-direction: row;
80
+ height: 100vh;
81
+ overflow: auto;
82
+ }
83
+
84
+ .text-secondary {
85
+ color: var(--color-secondary);
86
+ }
87
+
88
+ .filter-panel {
89
+ position: relative;
90
+ flex: 0.2;
91
+ border-right: solid 1px var(--border-color);
92
+ overflow-y: auto;
93
+ max-height: 100%;
94
+ }
95
+
96
+ .bg-faint {
97
+ background-color: var(--color-faint) !important;
98
+ }
99
+
100
+ .bg-secondary {
101
+ background-color: var(--color-secondary) !important;
102
+ }
103
+
104
+ .bg-highlight {
105
+ background-color: var(--color-highlight-light) !important;
106
+ }
107
+
108
+ .bg-backdrop {
109
+ background-color: var(--color-backdrop);
110
+ }
111
+
112
+ .bg-diff {
113
+ background-color: var(--color-diff);
114
+ }
115
+
116
+ .bg-primary {
117
+ background-color: var(--color-primary);
118
+ }
119
+
120
+ .border-primary {
121
+ border: 1px solid var(--color-primary);
122
+ }
123
+
124
+ .border-secondary {
125
+ border: 1px solid var(--color-secondary);
126
+ }
127
+
128
+ #thumbnails {
129
+ flex: 0.3;
130
+ overflow-y: auto;
131
+ max-height: 100%;
132
+ }
133
+
134
+ #comparisons {
135
+ flex: 1;
136
+ overflow-x: auto;
137
+ max-height: 100%;
138
+ }
139
+
140
+ #split {
141
+ width: 5px;
142
+ border-right: solid 1px var(--border-color);
143
+ cursor: ew-resize;
144
+ }
145
+
146
+ .skeleton {
147
+ animation: skeleton-loading 1s linear infinite alternate;
148
+ }
149
+
150
+ img {
151
+ width: 100%;
152
+ height: 100%;
153
+ object-fit: contain;
154
+ transition: opacity ease 0.3s;
155
+ }
156
+
157
+ @keyframes skeleton-loading {
158
+ 0% {
159
+ background-color: hsl(200, 20%, 80%);
160
+ }
161
+
162
+ 100% {
163
+ background-color: hsl(200, 20%, 95%);
164
+ }
165
+ }
166
+
167
+ .artboard {
168
+ background: url(https://percy.io/static/images/artboard-bg-c5ce8844100cad58983978e3d2794e06.svg);
169
+ }
170
+ </style>
171
+ </head>
172
+
173
+ <body>
174
+ <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
175
+ <!-- Load React. -->
176
+ <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
177
+ <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
178
+ <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
179
+
180
+ <main id="root"></main>
181
+
182
+ <script type="text/babel">
183
+ let BuildData = JSON.parse(atob(`<%- data %>`));
184
+
185
+ const PercyLogo = () => <a className="text-secondary" href="https://percy.io" target="_blank" ><svg width="31" height="24" fill="none" role="img"><path fillRule="evenodd" clipRule="evenodd" d="M30.111 5.456c-.173-.568-.63-1.127-1.173-.9-.319.134-.816.29-1.438.319-1.071-1.006-4.223-3-6.298-4.129 0 0 .253.74.603 2.124 0 0-2.234-1.745-4.762-2.87 0 0 .712 1.46.77 1.932 0 0-2.319-.807-5.572-1.113 0 0 1.562 1.09 2.168 1.923 0 0-2.299-.117-5.733 0 0 0 1.952.792 2.814 1.519 0 0-4.076.413-6.378 1.076 0 0 2.594.915 3.315 1.596 0 0-3.31.942-6.645 2.868 0 0 1.853.324 3.304.958 0 0-1.848 1.291-4.586 4.815 0 0 2.023-.386 3.412-.278 0 0-1.76 2.055-2.677 5.198 0 0 1.018-.648 2.003-.883 0 0 .106 3.748 2.155 4.349l.002-.003c.1.032.196.043.28.043.1 0 .204-.015.31-.046.65-.186 1.143-.828 1.713-1.57.186-.244.378-.494.582-.737.227-.322.524-.658.882-.953.724-.597 1.727-1.05 2.948-.868 1.375.116 2.225 1.486 2.907 2.587.356.573.825 1.587 1.612 1.587.87 0 1.176-1.042 1.563-2.328.363-1.208.911-2.371 1.612-3.415 1.357-2.02 3.094-3.118 5.175-4.2 1.995-1.037 3.88-2.017 4.824-3.504.473-.743.707-1.655.697-2.71-.01-.948-.217-1.821-.389-2.387zm-9.848-.581l-3.22-1.5 4.762 1.181-1.541.319zm-7.318.462l3.98 1.018 1.735-.576-5.715-.442zm7.846 1.995l.849-.643-3.827.811 2.978-.168zm-6.382 0l-1.36.912-3.773-.36 5.133-.552zm4.25 2.279l1.382-.772-5.133.552 3.752.22zm-3.802.857l-.962 1.582-4.816 1.178 5.778-2.76zm-6.017.974l-3.728 1.642L9.688 9.8l-.848 1.641zM7.55 13.41l-.146 1.642L5 17.25l2.55-3.84z" fill="currentColor"></path><path d="M20.249 23.593c-.418-.007-.863-.365-1.35-1.091.287-1.644 1.423-3.743 2.789-5.151-.349 1.307-.395 2.62-.433 3.676-.024.683-.045 1.272-.142 1.7-.13.575-.416.866-.85.866h-.014z" fill="currentColor"></path><path d="M8.676 22.406c.354.802.733 1.192 1.156 1.192a.66.66 0 0 0 .103-.008c.575-.091 1.107-1.205 1.552-2.282.116-.28.224-.56.321-.818-1.272.015-2.509 1.038-3.132 1.916z" fill="currentColor"></path></svg></a>;
186
+ const DiffIcon = () => <svg width="16" height="16" fill="none" role="img" aria-label="diff on"><path d="M8.13 4.675l-5.134.444a1.157 1.157 0 0 0-1.06 1.25l.661 7.482A1.163 1.163 0 0 0 3.861 14.9l5.135-.445a1.158 1.158 0 0 0 1.06-1.25l-.661-7.481a1.163 1.163 0 0 0-1.264-1.05z" fill="#999" fillOpacity=".2" stroke="#999" strokeWidth="1.5"></path><path d="M7.144 1.087l5.86.507a1.158 1.158 0 0 1 1.06 1.25l-.725 8.2a1.163 1.163 0 0 1-1.264 1.049l-5.86-.507a1.158 1.158 0 0 1-1.06-1.25l.725-8.2a1.163 1.163 0 0 1 1.264-1.05z" fill="currentColor" fillOpacity=".4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeDasharray="1.5 2.25"></path></svg>
187
+ const EmptyPlaceholder = () => <svg className="mx-auto mb-3 opacity-50" width="96" height="105" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M50.847 34.316l-34.876 3.06c-4.402.386-7.659 4.277-7.274 8.69l4.445 50.943c.385 4.413 4.266 7.678 8.669 7.292l34.875-3.06c4.403-.386 7.66-4.277 7.275-8.69l-4.445-50.943c-.386-4.413-4.267-7.678-8.669-7.292z" fill="#1D8DDB" fillOpacity=".1"></path><path d="M44.847 28.317L9.971 31.376c-4.402.386-7.659 4.277-7.274 8.69L7.142 91.01c.385 4.413 4.266 7.678 8.669 7.292l34.875-3.06c4.403-.386 7.66-4.277 7.275-8.69l-4.445-50.943c-.386-4.413-4.267-7.678-8.669-7.291z" stroke="#1D8DDB" strokeWidth="5.05"></path><path d="M88.028 14.185l-39.85-3.487c-4.403-.386-8.285 2.88-8.671 7.293l-4.895 55.936c-.386 4.413 2.87 8.303 7.272 8.688l39.85 3.488c4.403.385 8.285-2.88 8.671-7.294L95.3 22.873c.386-4.413-2.87-8.303-7.272-8.688z" fill="#1D8DDB" fillOpacity=".1"></path><path d="M81.822 7.185l-39.85-3.487c-4.403-.386-8.285 2.88-8.671 7.293l-4.895 55.936c-.386 4.413 2.87 8.303 7.272 8.688l39.85 3.488c4.403.385 8.285-2.88 8.671-7.294l4.895-55.936c.386-4.413-2.87-8.303-7.272-8.688z" stroke="#1D8DDB" strokeWidth="5.05" strokeLinecap="round" strokeDasharray="0.44 0.44 8.69 10.99"></path></svg>
188
+ const Empty = (props) => (
189
+ <div className="artboard w-full h-full flex items-center justify-center flex-col gap-4" >
190
+ <EmptyPlaceholder />
191
+ <h4 className="text-xl" >{props.children}</h4>
192
+ </div>
193
+ )
194
+ const LayoutDiff = () => <svg className="layout-diff-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none"><path d="M2.25 3.75v10.5a1.5 1.5 0 0 0 1.5 1.5h4.5V2.25h-4.5a1.5 1.5 0 0 0-1.5 1.5zm4.5 10.5h-3V3.75h3v10.5z" fill="#64748B"></path><path d="M14.25 2.25h-4.5v6h6v-4.5c0-.825-.675-1.5-1.5-1.5zm0 4.5h-3v-3h3v3z" fill="#64748B"></path><path d="M9.75 15.75h4.5c.825 0 1.5-.675 1.5-1.5v-4.5h-6v6zm1.5-4.5h3v3h-3v-3z" fill="#64748B"></path></svg>
195
+ const SnapshotData = BuildData.details.reduce((map, current) => {
196
+ if (current.comparisons) {
197
+ let diffRatios = current.comparisons.map((c) => Number(c['diff-percentage']))
198
+ current['diff-ratios'] = diffRatios;
199
+ current['max-diff'] = Math.max(...diffRatios);
200
+ const isChanged = current.comparisons.some((c)=>c.images && (c.images.diff || !c.images.base))
201
+ if (isChanged) {
202
+ map.changed.push(current)
203
+ } else {
204
+ map.unchanged.push(current)
205
+ }
206
+ }
207
+ return map
208
+ }, {
209
+ unchanged: [],
210
+ changed: []
211
+ });
212
+
213
+ const Browsers = {
214
+ "Safari": <svg className="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="38.698" cy="38.568" r="38" fill="#BABABA"></circle><circle cx="38.698" cy="38.568" r="38" fill="url(#x)"></circle><circle cx="38.698" cy="38.568" r="38" fill="url(#w)"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="#309CFF"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="url(#cc)"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="url(#dd)"></circle><path fillRule="evenodd" clipRule="evenodd" d="M37.168 35.58L19.705 58.046 39.668 38.08l-2.501-2.501z" fill="#fff"></path><path d="M19.704 58.046L42.17 40.582l-2.5-2.501-19.966 19.965z" fill="#fff"></path><path d="M42.166 40.59l-2.629-2.641-19.816 20.096L42.166 40.59z" fill="#E5E5E5"></path><path d="M37.19 35.59l22.254-17.298-17.362 22.336-.184.047-4.809-4.809.101-.275z" fill="#EA3939"></path><path d="M42.159 40.574l-2.641-2.63 20.096-19.816-17.455 22.446z" fill="#AD2C2C"></path><rect x="38.698" y="8.255" width="1.64" height="5.66" rx=".82" fill="#E8F4FF"></rect><rect x="38.698" y="63.584" width="1.64" height="5.66" rx=".82" fill="#E8F4FF"></rect><rect x="69.013" y="37.93" width="1.64" height="5.66" rx=".82" transform="rotate(90 69.013 37.93)" fill="#E8F4FF"></rect><rect x="13.683" y="37.93" width="1.64" height="5.66" rx=".82" transform="rotate(90 13.683 37.93)" fill="#E8F4FF"></rect><rect x="60.661" y="59.733" width="1.64" height="5.66" rx=".82" transform="rotate(135 60.66 59.733)" fill="#E8F4FF"></rect><rect x="21.537" y="20.609" width="1.64" height="5.66" rx=".82" transform="rotate(135 21.537 20.61)" fill="#E8F4FF"></rect><defs><radialGradient id="w" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .065 38.633) scale(38)"><stop offset=".927" stopColor="#B6C6FF" stopOpacity="0"></stop><stop offset="1" stopColor="#CFCFD1"></stop></radialGradient><radialGradient id="cc" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(52.933 -10.487 32.545) scale(56.3527)"><stop offset=".594" stopColor="#60D9FF" stopOpacity=".59"></stop><stop offset="1" stopColor="#003CB1" stopOpacity=".83"></stop></radialGradient><radialGradient id="dd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .065 38.633) scale(32.7068)"><stop offset=".87" stopColor="#5587E8" stopOpacity="0"></stop><stop offset="1" stopColor="#175ECA"></stop></radialGradient><linearGradient id="x" x1="12.796" y1="12.307" x2="65.319" y2="65.307" gradientUnits="userSpaceOnUse"><stop stopColor="#fff" stopOpacity=".79"></stop><stop offset="1" stopColor="#fff" stopOpacity="0"></stop></linearGradient></defs></svg>,
215
+ "Firefox": <svg className="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M36.977 0c20.42 0 36.976 16.464 36.976 36.778 0 20.308-16.555 36.777-36.976 36.777S0 57.086 0 36.778C0 16.462 16.556 0 36.977 0z" transform="translate(2.465)" fill="url(#a)"></path><path d="M45.762 70.558C64.532 67.545 78.886 51.4 78.886 31.88l-.36.444c.562-3.71.464-7.015-.308-9.898-.276 2.136-.577 3.425-.88 3.943-.002-.167-.044-2.393-.757-5.5-.338-2.265-.833-4.396-1.518-6.365.167.902.266 1.655.315 2.337C72.514 9.196 65.461-.217 48.084.004c0 0 6.111.642 8.988 4.975 0 0-2.94-.7-5.164.372 2.709 1.074 5.058 2.197 7.057 3.37l.173.105c.513.306.95.62 1.413.931 3.692 2.58 7.119 6.26 6.858 11.007-.797-1.25-1.857-2.07-3.215-2.482 1.674 6.522 1.839 11.909.486 16.158-.93-2.83-1.785-4.526-2.547-5.124 1.063 8.743-.37 15.209-4.287 19.423.745-2.572 1.04-4.678.873-6.34-4.602 6.919-9.838 10.496-15.708 10.717-2.32-.02-4.51-.368-6.572-1.045-3.027-1.017-5.766-2.756-8.204-5.222 3.806.316 7.282-.34 10.342-1.893l5.011-3.266-.02-.014c.651-.246 1.263-.226 1.86.05 1.222-.166 1.65-.818 1.242-1.919-.592-.819-1.487-1.562-2.636-2.216-2.507-1.302-5.125-1.096-7.856.637-2.6 1.471-5.103 1.412-7.538-.152-1.595-1.096-3.136-2.572-4.625-4.416l-.591-.87c-.28 2.089.037 4.769.98 8.069l.02.044-.02-.04c-.946-3.3-1.26-5.986-.98-8.073v-.02c.071-1.824.83-2.83 2.277-3.055l-.614-.051.62.051c1.634.15 3.51.523 5.625 1.136.355-2.03-.111-4.156-1.39-6.336v-.04c1.986-1.843 3.749-3.184 5.255-4.04.668-.355 1.057-.895 1.186-1.623l.054-.04.02-.019.074-.071c.389-.579.258-1.044-.407-1.456a16.664 16.664 0 0 1-4.179-.297l-.02.056c-.574-.166-1.298-.674-2.19-1.532l-2.286-2.236-.685-.537v.071h-.02l.02-.09-.13-.136.184-.13c.316-1.694.836-3.15 1.578-4.4l.167-.15c.745-1.231 2.172-2.553 4.27-3.965-3.9.483-7.427 2.231-10.563 5.254-2.6-.948-5.682-.747-9.23.613l-.426.324-.032.017.463-.338.02-.02C10.835 8.71 9.33 5.78 8.605.992 5.766 3.789 4.39 8.784 4.483 15.995l-.814 1.223-.21.142-.041.04-.018.017-.04.08c-.43.673-1.025 1.688-1.774 3.05C.508 22.476.145 24.094.04 25.504l-.01.017.006.047-.035.37.062-.098c.007.326.014.655.098.949l2.303-1.883c-.836 2.106-1.39 4.34-1.672 6.708l-.066 1.084-.723-.822c0 8.405 2.683 16.175 7.208 22.56l.136.21.217.258a39.539 39.539 0 0 0 12.222 10.945c3.459 2.038 7.176 3.465 11.14 4.313l.816.181c.82.157 1.661.275 2.504.38.624.081 1.248.16 1.879.214l.838.095 1.19.008 1.295.064 1.03-.052 1.699-.083a39.852 39.852 0 0 0 2.987-.322l.6-.088zM70.704 22.77l-.017.321.012-.323.005.002z" transform="translate(0 4.897)" fill="url(#b)"></path><defs><linearGradient id="a" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 73.5555 -73.9534 0 73.953 0)"><stop stopColor="#0093F7"></stop><stop offset="1" stopColor="#372893"></stop></linearGradient><linearGradient id="b" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(78.8861 0 0 88.4522 0 -9.471)"><stop stopColor="#FE3229"></stop><stop offset="1" stopColor="#FFD71F"></stop></linearGradient></defs></svg>,
216
+ "Chrome": <svg className="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M38 57.447c-3.86 0-7.372-1.04-10.54-3.117-3.169-2.078-5.544-4.75-7.125-8.015L4.75 19C1.484 24.84 0 31.371 0 38c0 9.5 3.092 17.789 9.277 24.864 6.184 7.075 13.88 11.305 23.082 12.692l11.023-19.074c-1.11.326-2.96.965-5.382.965z" fill="#4AAE48"></path><path d="M26.05 22.636C29.562 19.916 33.547 19 38 19h32.656c-3.366-5.738-7.942-10.588-13.73-13.953C51.136 1.684 44.828 0 37.999 0c-5.937 0-11.48 1.261-16.625 3.786-5.146 2.522-9.908 6.196-13.397 10.91L18.999 33.25c1.088-4.254 3.537-7.89 7.052-10.614z" fill="#EA3939"></path><path d="M73.143 23.75h-22.08c3.86 3.86 6.384 8.809 6.384 14.25 0 4.059-1.138 7.769-3.415 11.134L38.447 76c10.39-.1 19.247-3.86 26.571-11.281C72.338 57.297 76 48.39 76 38c0-4.847-.817-9.947-2.857-14.25z" fill="#FED14B"></path><path d="M38 52.25c7.87 0 14.25-6.38 14.25-14.25S45.87 23.75 38 23.75 23.75 30.13 23.75 38 30.13 52.25 38 52.25z" fill="#188FD1"></path></svg>,
217
+ "Edge": <svg className="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><g clipPath="url(#z)"><path d="M68.584 56.56c-1.014.53-2.058.997-3.129 1.399a30.242 30.242 0 0 1-10.657 1.917c-14.046 0-26.282-9.662-26.282-22.061a9.343 9.343 0 0 1 4.877-8.107c-12.705.535-15.97 13.774-15.97 21.53 0 21.931 20.212 24.154 24.567 24.154 2.348 0 5.889-.682 8.014-1.353l.39-.13a38.096 38.096 0 0 0 19.769-15.674 1.187 1.187 0 0 0-1.58-1.674z" fill="url(#y)"></path><path opacity=".35" d="M68.584 56.56c-1.014.53-2.058.997-3.129 1.399a30.242 30.242 0 0 1-10.657 1.917c-14.046 0-26.282-9.662-26.282-22.061a9.343 9.343 0 0 1 4.877-8.107c-12.705.535-15.97 13.774-15.97 21.53 0 21.931 20.212 24.154 24.567 24.154 2.348 0 5.889-.682 8.014-1.353l.39-.13a38.096 38.096 0 0 0 19.769-15.674 1.187 1.187 0 0 0-1.58-1.674z" fill="url(#c)"></path><path d="M31.378 71.663a23.51 23.51 0 0 1-6.75-6.334 23.96 23.96 0 0 1 8.765-35.621c.926-.437 2.508-1.226 4.613-1.188a9.603 9.603 0 0 1 7.626 3.86 9.463 9.463 0 0 1 1.888 5.538c0-.062 7.26-23.628-23.748-23.628C10.741 14.29.025 26.656.025 37.506A38.634 38.634 0 0 0 3.62 54.129a37.996 37.996 0 0 0 46.42 19.92 22.426 22.426 0 0 1-18.636-2.374l-.026-.012z" fill="url(#d)"></path><path opacity=".41" d="M31.378 71.663a23.51 23.51 0 0 1-6.75-6.334 23.96 23.96 0 0 1 8.765-35.621c.926-.437 2.508-1.226 4.613-1.188a9.603 9.603 0 0 1 7.626 3.86 9.463 9.463 0 0 1 1.888 5.538c0-.062 7.26-23.628-23.748-23.628C10.741 14.29.025 26.656.025 37.506A38.634 38.634 0 0 0 3.62 54.129a37.996 37.996 0 0 0 46.42 19.92 22.426 22.426 0 0 1-18.636-2.374l-.026-.012z" fill="url(#e)"></path><path d="M45.211 44.188c-.24.312-.98.742-.98 1.68 0 .775.505 1.52 1.402 2.146 4.268 2.969 12.316 2.577 12.336 2.577a17.68 17.68 0 0 0 8.986-2.479 18.22 18.22 0 0 0 9.033-15.697c.077-6.652-2.375-11.075-3.366-13.034C66.332 7.077 52.754 0 37.989 0A37.996 37.996 0 0 0-.007 37.462c.143-10.847 10.924-19.607 23.748-19.607 1.038 0 6.963.101 12.467 2.99 4.85 2.546 7.391 5.622 9.158 8.67 1.834 3.167 2.16 7.169 2.16 8.763 0 1.594-.813 3.957-2.315 5.91z" fill="url(#f)"></path><path d="M45.211 44.188c-.24.312-.98.742-.98 1.68 0 .775.505 1.52 1.402 2.146 4.268 2.969 12.316 2.577 12.336 2.577a17.68 17.68 0 0 0 8.986-2.479 18.22 18.22 0 0 0 9.033-15.697c.077-6.652-2.375-11.075-3.366-13.034C66.332 7.077 52.754 0 37.989 0A37.996 37.996 0 0 0-.007 37.462c.143-10.847 10.924-19.607 23.748-19.607 1.038 0 6.963.101 12.467 2.99 4.85 2.546 7.391 5.622 9.158 8.67 1.834 3.167 2.16 7.169 2.16 8.763 0 1.594-.813 3.957-2.315 5.91z" fill="url(#g)"></path></g><defs><radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(28.3129 0 0 26.8972 46.662 52.974)"><stop offset=".72" stopOpacity="0"></stop><stop offset=".95" stopOpacity=".53"></stop><stop offset="1"></stop></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-81.384 44.823 17.356) scale(42.575 34.3963)"><stop offset=".76" stopOpacity="0"></stop><stop offset=".95" stopOpacity=".5"></stop><stop offset="1"></stop></radialGradient><radialGradient id="f" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(92.291 -2.921 10.717) scale(60.1379 128.081)"><stop stopColor="#35C1F1"></stop><stop offset=".11" stopColor="#34C1ED"></stop><stop offset=".23" stopColor="#2FC2DF"></stop><stop offset=".31" stopColor="#2BC3D2"></stop><stop offset=".67" stopColor="#36C752"></stop></radialGradient><radialGradient id="g" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(73.74 20.31 58.993) scale(28.8947 23.4971)"><stop stopColor="#66EB6E"></stop><stop offset="1" stopColor="#66EB6E" stopOpacity="0"></stop></radialGradient><linearGradient id="y" x1="17.423" y1="52.556" x2="70.362" y2="52.556" gradientUnits="userSpaceOnUse"><stop stopColor="#0C59A4"></stop><stop offset="1" stopColor="#114A8B"></stop></linearGradient><linearGradient id="d" x1="45.332" y1="29.592" x2="12.267" y2="65.608" gradientUnits="userSpaceOnUse"><stop stopColor="#1B9DE2"></stop><stop offset=".16" stopColor="#1595DF"></stop><stop offset=".67" stopColor="#0680D7"></stop><stop offset="1" stopColor="#0078D4"></stop></linearGradient><clipPath id="z"><path fill="#fff" d="M0 0h76v76H0z"></path></clipPath></defs></svg>,
218
+ "Safari on iPhone": <svg className="w-full browser-icon" width="100" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clipPath="url(#aaa)"><path d="M28.375 0h-16.25A3.09 3.09 0 0 0 9 3.047v33.906A3.09 3.09 0 0 0 12.125 40h16.25a3.09 3.09 0 0 0 3.125-3.047V3.047A3.09 3.09 0 0 0 28.375 0zm1.875 36.953a1.84 1.84 0 0 1-1.875 1.796h-16.25a1.84 1.84 0 0 1-1.875-1.796V3.047a1.84 1.84 0 0 1 1.875-1.797h3.125A1.301 1.301 0 0 0 16.5 2.5H24a1.343 1.343 0 0 0 1.25-1.25h3.125a1.84 1.84 0 0 1 1.875 1.797v33.906z" fill="#000"></path><path d="M30.25 36.953a1.84 1.84 0 0 1-1.875 1.796h-16.25a1.84 1.84 0 0 1-1.875-1.796V3.047a1.84 1.84 0 0 1 1.875-1.797h3.125A1.301 1.301 0 0 0 16.5 2.5H24a1.343 1.343 0 0 0 1.25-1.25h3.125a1.84 1.84 0 0 1 1.875 1.797v33.906z" fill="#BFD2EE"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="#BABABA"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="url(#bbb)"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="url(#ccc)"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="#309CFF"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="url(#ddd)"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="url(#eee)"></path><path fillRule="evenodd" clipRule="evenodd" d="M19.88 19.505l-3.416 4.394 3.905-3.905-.49-.49h.001z" fill="#fff"></path><path d="M16.464 23.9l4.394-3.417-.49-.49-3.904 3.906z" fill="#fff"></path><path d="M20.856 20.484l-.514-.517-3.876 3.93 4.39-3.413z" fill="#E5E5E5"></path><path d="M19.882 19.508l4.353-3.383-3.396 4.369-.036.009-.94-.94.02-.055h-.001z" fill="#EA3939"></path><path d="M20.854 20.482l-.517-.514 3.93-3.876-3.413 4.39z" fill="#AD2C2C"></path><path d="M20.498 14.32a.16.16 0 1 0-.32 0v.786a.16.16 0 0 0 .32 0v-.786zm0 10.823a.16.16 0 1 0-.32 0v.787a.16.16 0 0 0 .32 0v-.787zm5.45-4.86a.16.16 0 0 0 0-.32h-.787a.16.16 0 1 0 0 .32h.787zm-10.661-.16a.16.16 0 0 0-.16-.16h-.786a.16.16 0 1 0 0 .32h.786a.16.16 0 0 0 .16-.16zm8.847 4.217a.16.16 0 0 0 .227-.227l-.556-.556a.16.16 0 0 0-.274.114.162.162 0 0 0 .047.113l.556.556zm-7.427-7.652a.16.16 0 0 0 0-.226l-.556-.556a.16.16 0 1 0-.227.226l.556.556a.161.161 0 0 0 .227 0z" fill="#E8F4FF"></path></g><defs><radialGradient id="ccc" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .045 20.134) scale(7.43234)"><stop offset=".927" stopColor="#B6C6FF" stopOpacity="0"></stop><stop offset="1" stopColor="#CFCFD1"></stop></radialGradient><radialGradient id="ddd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(52.933 -8.346 25.3) scale(11.0219)"><stop offset=".594" stopColor="#60D9FF" stopOpacity=".59"></stop><stop offset="1" stopColor="#003CB1" stopOpacity=".83"></stop></radialGradient><radialGradient id="eee" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .044 20.132) scale(6.39705)"><stop offset=".87" stopColor="#5587E8" stopOpacity="0"></stop><stop offset="1" stopColor="#175ECA"></stop></radialGradient><linearGradient id="bbb" x1="15.113" y1="14.952" x2="25.386" y2="25.319" gradientUnits="userSpaceOnUse"><stop stopColor="#fff" stopOpacity=".79"></stop><stop offset="1" stopColor="#fff" stopOpacity="0"></stop></linearGradient><clipPath id="aaa"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath></defs></svg>,
219
+ "Chrome on Android": <svg className="w-full browser-icon" width="100" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clipPath="url(#aaaa)"><path d="M10.152.38H28.62v39.298H10.152V.381z" fill="#A4A9AF"></path><g filter="url(#bbbb)"><path d="M19.425 25.791a5.531 5.531 0 1 0 0-11.062 5.531 5.531 0 0 0 0 11.062z" fill="#F9F9F9"></path><path d="M19.425 23.09a2.743 2.743 0 0 1-1.535-.453 2.88 2.88 0 0 1-1.037-1.168l-2.268-3.975c-.476.85-.694 1.8-.694 2.766 0 1.383.452 2.59 1.353 3.619.899 1.03 2.019 1.646 3.36 1.848l1.604-2.776c-.162.046-.43.14-.783.14z" fill="#4AAE48"></path><path d="M17.684 18.024c.51-.396 1.091-.53 1.739-.53h4.754a5.576 5.576 0 0 0-2-2.03 5.39 5.39 0 0 0-2.754-.735c-.865 0-1.67.183-2.42.552a5.35 5.35 0 0 0-1.951 1.587l1.605 2.7a2.82 2.82 0 0 1 1.027-1.544z" fill="#EA3939"></path><path d="M24.54 18.186h-3.214c.562.562.93 1.282.93 2.077 0 .59-.166 1.13-.498 1.619l-2.27 3.912a5.311 5.311 0 0 0 3.868-1.644c1.067-1.08 1.6-2.377 1.6-3.89 0-.704-.12-1.447-.417-2.074z" fill="#FED14B"></path><path d="M19.425 22.335a2.075 2.075 0 0 0 1.525-3.573 2.075 2.075 0 1 0-1.525 3.573z" fill="#188FD1"></path></g><path d="M10.076.19h18.505" stroke="#435553" strokeWidth=".5"></path><path d="M10.038 39.985v-.381M10 .19h.114H10zm.038.191V0v.381zm0 0v39.566V.38zM10 .191h.114H10zm.038.19V0v.381zM28.62.191h.114-.114zm.038.19V0v.381zM10 39.794h.114H10z" stroke="#4E494B" strokeWidth=".5"></path><path d="M10.038.38v39.567" stroke="#2E403E" strokeWidth=".5"></path><path d="M28.651 39.81l-18.504.012" stroke="#3C4F4D" strokeWidth=".5"></path><path d="M28.688 39.62V40m.038-.19h-.114.114z" stroke="#4E494B" strokeWidth=".5"></path><path d="M28.688 39.62L28.667.053" stroke="#2E403E" strokeWidth=".5"></path><path d="M29.168 14.826v2.442m0-9.766v4.88-4.88z" stroke="#0A2C28" strokeWidth=".15"></path><path fillRule="evenodd" clipRule="evenodd" d="M19.585 2.501a.417.417 0 1 0 0-.834.417.417 0 0 0 0 .834z" fill="#212022"></path><g filter="url(#cccc)"><path fillRule="evenodd" clipRule="evenodd" d="M19.583 2.342a.26.26 0 1 0 .044-.519.26.26 0 0 0-.044.52z" fill="url(#dddd)"></path></g><g filter="url(#eeee)"><path fillRule="evenodd" clipRule="evenodd" d="M19.58 2.24a.156.156 0 1 0 .001-.312.156.156 0 0 0 0 .312z" fill="url(#ffff)"></path></g><mask id="gggg" style={{ maskType: "alpha" }} maskUnits="userSpaceOnUse" x="19" y="1" width="1" height="2"><path fillRule="evenodd" clipRule="evenodd" d="M19.58 2.24a.156.156 0 1 0 .001-.312.156.156 0 0 0 0 .312z" fill="#fff"></path></mask><g mask="url(#gggg)"><g filter="url(#hhhh)"><path fillRule="evenodd" clipRule="evenodd" d="M19.583 2.261a.166.166 0 1 0 0-.333.166.166 0 0 0 0 .333z" fill="#1A1719"></path></g><g opacity=".776" filter="url(#iiii)"><path opacity=".776" fillRule="evenodd" clipRule="evenodd" d="M19.583 2.033c.07 0 .126-.029.126-.063s-.058-.063-.126-.063c-.069 0-.124.029-.124.063s.057.063.124.063z" fill="url(#jjjj)"></path></g><g filter="url(#kkkk)"><path fillRule="evenodd" clipRule="evenodd" d="M19.579 2.293c.044 0 .076-.02.076-.046 0-.027-.034-.048-.076-.048-.044 0-.078.021-.078.048 0 .025.034.046.076.046h.002z" fill="url(#llll)"></path></g></g></g><defs><filter id="bbbb" x="13.892" y="14.729" width="11.065" height="15.065" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="4"></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"></feColorMatrix><feBlend in2="shape" result="effect1_innerShadow_803_329"></feBlend></filter><filter id="cccc" x="15.344" y="-2.179" width="8.522" height="8.521" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_803_329"></feBlend><feBlend in="SourceGraphic" in2="effect1_dropShadow_803_329" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="65"></feOffset><feGaussianBlur stdDeviation="1.5"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="shape" result="effect2_innerShadow_803_329"></feBlend></filter><filter id="eeee" x="15.425" y="-2.072" width="8.312" height="8.313" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_803_329"></feBlend><feBlend in="SourceGraphic" in2="effect1_dropShadow_803_329" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="1"></feOffset><feGaussianBlur stdDeviation="1.5"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="shape" result="effect2_innerShadow_803_329"></feBlend></filter><filter id="hhhh" x="14.252" y="-3.236" width="10.661" height="10.661" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="2.582" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><filter id="iiii" x="11.305" y="-6.247" width="16.558" height="16.434" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="4.077" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><filter id="kkkk" x="14.337" y="-2.965" width="10.482" height="10.422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="2.582" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><radialGradient id="dddd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.583 2.083) scale(.26042)"><stop offset=".24" stopColor="#8F8F8F"></stop><stop offset=".5" stopColor="#090A0F"></stop><stop offset=".998" stopColor="#090A0F"></stop></radialGradient><radialGradient id="ffff" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.58 2.084) scale(.15625)"><stop offset=".092" stopColor="#242C33"></stop><stop offset=".289" stopColor="#1A1719"></stop><stop offset="1" stopColor="#1A1719"></stop></radialGradient><radialGradient id="jjjj" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .0929 -.18581 0 19.583 1.972)"><stop stopColor="#65BBFF"></stop><stop offset=".581" stopColor="#E0DDE7"></stop><stop offset="1" stopColor="#968BAC"></stop></radialGradient><radialGradient id="llll" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .07918 -.13196 0 19.579 2.173)"><stop stopColor="#88888A"></stop><stop offset=".558" stopColor="#8B8B8B"></stop><stop offset="1" stopColor="#505050"></stop></radialGradient><clipPath id="aaaa"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath></defs></svg>
220
+ }
221
+
222
+ const BrowserSegments = function (props) {
223
+ return (
224
+ <div className="flex gap-2">
225
+ {props.browsers.map((browser) => {
226
+ let active = props.activeBrowser == browser
227
+ return <button
228
+ key={browser}
229
+ onClick={() => props.onChange(browser)}
230
+ style={{ width: 30, height: 30 }}
231
+ className={`p-1 rounded ${active ? 'bg-highlight border-primary' : ''}`} >
232
+ {Browsers[browser]}
233
+ </button>
234
+ })}
235
+ </div>
236
+ )
237
+ }
238
+
239
+ const Picker = function (props) {
240
+ React.useEffect(() => {
241
+ if (props.items.length > 0 && !props.items.includes(props.value)) {
242
+ props.onChange(props.items[0])
243
+ }
244
+ }, [props])
245
+ if (!props.items || props.items.length == 0) {
246
+ return null
247
+ }
248
+ return (
249
+ <div className="flex gap-2">
250
+ <select onChange={(e) => props.onChange(e.target.value)} className="border-primary bg-highlight p-2 rounded" value={props.value} >
251
+ {props.items.map((width) => {
252
+ return (
253
+ <option key={width} >{width}</option>
254
+ )
255
+ })}
256
+ </select>
257
+ </div>
258
+ )
259
+ }
260
+
261
+ const BaseImage = (props) => {
262
+ const [loading, SetLoading] = React.useState(false)
263
+ const image = React.useRef()
264
+ React.useEffect(() => {
265
+ setTimeout(() => {
266
+ SetLoading(image.current ? !image.current.complete : true)
267
+ })
268
+ }, [props.src])
269
+ if (!props.src) {
270
+ return <Empty>No Base Image</Empty>
271
+ }
272
+ return <div className={`w-full h-full ${loading ? 'skeleton' : 'artboard'}`}>
273
+ <img ref={image} style={{ opacity: loading ? 0 : 1 }} onLoad={() => {
274
+ SetLoading(false)
275
+ }} {...props} />
276
+ </div>
277
+ }
278
+
279
+ const HeadImage = (props) => {
280
+ const [loadingHead, SetLoadingHead] = React.useState(false)
281
+ const [loadingDIFF, SetLoadingDiff] = React.useState(false)
282
+ const headImage = React.useRef()
283
+ const diffImage = React.useRef()
284
+ const loading = loadingHead
285
+ React.useEffect(() => {
286
+ setTimeout(() => {
287
+ SetLoadingHead(headImage.current ? !headImage.current.complete : true)
288
+ })
289
+ }, [props.headSrc])
290
+
291
+ React.useEffect(() => {
292
+ setTimeout(() => {
293
+ SetLoadingDiff(diffImage.current ? !diffImage.current.complete : true)
294
+ })
295
+ }, [props.diffSrc])
296
+ if (!props.headSrc) {
297
+ return <Empty>No Image</Empty>
298
+ }
299
+ return (
300
+ <div className={`relative w-full h-full ${loading ? 'skeleton' : 'artboard'}`}>
301
+ <img ref={headImage} style={{ opacity: loading ? 0 : 1 }} onLoad={() => {
302
+ SetLoadingHead(false)
303
+ }} src={props.headSrc} />
304
+ <img className="absolute top-0 left-0 right-0 bottom-0 bg-backdrop" ref={diffImage} style={{ opacity: loading || (!props.diffOverlay || !props.diffSrc) ? 0 : 1 }} onLoad={() => {
305
+ SetLoadingDiff(false)
306
+ }} src={props.diffSrc || ''} />
307
+ </div>
308
+ )
309
+ }
310
+
311
+ function App() {
312
+ const [filters, SetFilters] = React.useState(() => {
313
+ const snapshot = BuildData.details[0]
314
+ const comparison = snapshot && snapshot.comparisons[0]
315
+ return {
316
+ snapshots: 'changed',
317
+ diffThreshold: 0,
318
+ search: '',
319
+ activeSnapshot: snapshot ? BuildData.details[0].name : '',
320
+ activeBrowser: comparison ? comparison.browser : null,
321
+ activeWidth: comparison ? comparison.width : null,
322
+ activeDevice: comparison ? comparison.device : null
323
+ }
324
+ })
325
+ const [diffOverlay, SetDiffOverlay] = React.useState(true);
326
+ const SetFilter = (state) => {
327
+ SetFilters(Object.assign({}, filters, state))
328
+ }
329
+
330
+ const snapshots = React.useMemo(() => {
331
+ let category = SnapshotData[filters.snapshots]
332
+ let snapshots = category
333
+ if (filters.snapshots == 'changed') {
334
+ snapshots = snapshots.filter((snapshot) => snapshot['max-diff'] >= filters.diffThreshold)
335
+ }
336
+ if (filters.search) {
337
+ snapshots = snapshots.filter((snapshot) => snapshot.name.toLowerCase().includes(filters.search.toLowerCase()))
338
+ }
339
+ snapshots = snapshots.sort((a, b) => Number(b['max-diff']) - Number(a['max-diff']))
340
+ return snapshots
341
+ }, [filters])
342
+
343
+ const activeSnapshot = React.useMemo(() => {
344
+ let index = snapshots.findIndex((s) => s.name == filters.activeSnapshot);
345
+ if (index < 0 && snapshots.length == 0) {
346
+ return undefined
347
+ }
348
+ if (index < 0) {
349
+ index = 0
350
+ }
351
+ const snapshot = snapshots[index]
352
+ const details = snapshot.comparisons.reduce((map, current) => {
353
+ if (current.browser && !map.browsers.includes(current.browser)) {
354
+ map.browsers.push(current.browser)
355
+ if (!map.widths[current.browser]) {
356
+ map.widths[current.browser] = []
357
+ }
358
+ }
359
+ if (current.browser && current.width && !map.widths[current.browser].includes(current.width)) {
360
+ map.widths[current.browser].push(current.width)
361
+ }
362
+ if (current.device && !map.devices.includes(current.device)) {
363
+ map.devices.push(current.device)
364
+ }
365
+ return map
366
+ }, {
367
+ browsers: [],
368
+ widths: {},
369
+ devices: []
370
+ })
371
+ return {
372
+ index,
373
+ snapshot: snapshots[index],
374
+ ...details
375
+ }
376
+ }, [snapshots, filters])
377
+
378
+ const activeComparison = React.useMemo(() => {
379
+ if (activeSnapshot && activeSnapshot.snapshot) {
380
+ return activeSnapshot.snapshot.comparisons.find((c) => {
381
+ if (c.device) {
382
+ return c.device == filters.activeDevice
383
+ } else {
384
+ return c.browser == filters.activeBrowser && c.width == filters.activeWidth && c.device == filters.activeDevice
385
+ }
386
+ })
387
+ }
388
+ }, [activeSnapshot, filters])
389
+
390
+ React.useEffect(() => {
391
+ if (activeSnapshot && activeSnapshot.snapshot.name != filters.activeSnapshot) {
392
+ SetFilter({ activeSnapshot: activeSnapshot.snapshot.name })
393
+ }
394
+ }, [activeSnapshot, filters])
395
+
396
+ const NextSnapshot = () => {
397
+ if (activeSnapshot && activeSnapshot.index < snapshots.length - 1) {
398
+ SetFilter({ activeSnapshot: snapshots[activeSnapshot.index + 1].name })
399
+ }
400
+ }
401
+ const PrevSnapshot = () => {
402
+ if (activeSnapshot && activeSnapshot.index > 0) {
403
+ SetFilter({ activeSnapshot: snapshots[activeSnapshot.index - 1].name })
404
+ }
405
+ }
406
+ const getImage = (comp, kind) => {
407
+ if (comp && comp.images[kind]) {
408
+ return comp.images[kind].file || comp.images[kind].url
409
+ }
410
+ }
411
+ return (
412
+ <div className="app">
413
+ <div className="filter-panel">
414
+ <div className="p-2">
415
+ <PercyLogo />
416
+ </div>
417
+ <div className="p-2 flex justify-between">
418
+ <h6><b>Build #{BuildData.buildNumber}</b></h6>
419
+ <a target="_blank" href={BuildData.buildURL}><i className="fa fa-external-link"></i></a>
420
+ </div>
421
+ <div className="p-2 flex flex-col">
422
+ <div className="font-bold mb-2">Snapshots</div>
423
+ {["changed", "unchanged"].map((item, i) => {
424
+ const active = filters.snapshots == item
425
+ return (
426
+ <button key={i} onClick={() => SetFilter({ snapshots: item })} className={`flex p-1 rounded ${active ? 'bg-highlight border-primary' : ''}`}>
427
+ <div className="text-start flex-1 capitalize">{item}</div>
428
+ <div >{SnapshotData[item].length}</div>
429
+ </button>
430
+ )
431
+ })}
432
+ </div>
433
+ {filters && filters.snapshots == 'changed' ? <div className="p-2 flex flex-col">
434
+ <div className="font-bold mb-2 flex justify-between">
435
+ <span>Diff Threshold</span>
436
+ <span>{">"}</span>
437
+ <span>{filters.diffThreshold}%</span>
438
+ </div>
439
+ <input onChange={(e) => SetFilter({ diffThreshold: Number(e.target.value) })} type="range" min="0" max="100" value={filters.diffThreshold} />
440
+ </div> : null}
441
+ <div className="p-2 flex flex-col">
442
+ <div className="font-bold mb-2">Metadata</div>
443
+ <div className={`flex p-1 rounded`}>
444
+ <p className="text-start flex-1 capitalize">Total Snapshots</p>
445
+ <p >{BuildData['totalSnapshots']}</p>
446
+ </div>
447
+ <div className={`flex p-1 rounded`}>
448
+ <p className="text-start flex-1 capitalize">Unreviewed Snapshots</p>
449
+ <p >{BuildData['unreviewedScreenshots']}</p>
450
+ </div>
451
+ </div>
452
+ </div>
453
+ <div id="thumbnails" className="p-2">
454
+ <input onChange={(e) => SetFilter({ search: e.target.value })} className="w-full mb-3 sticky top-0" type="text" placeholder="Search Snapshot" />
455
+ <div className="flex flex-col overflow-y-auto flex-1 gap-2">
456
+ {snapshots.length > 0 ? snapshots.map((snapshot) => {
457
+ const active = snapshot.name == filters.activeSnapshot
458
+ return (
459
+ <button key={snapshot.name} onClick={() => SetFilter({ activeSnapshot: snapshot.name })} className={`flex p-1 rounded ${active ? 'bg-highlight border-primary' : ''}`}>
460
+ <div className="text-start flex-1">{snapshot['name']}</div>
461
+ <div>{snapshot['max-diff']}%</div>
462
+ </button>
463
+ )
464
+ }) : <EmptyPlaceholder />}
465
+ </div>
466
+ </div>
467
+ <div id="split" />
468
+ <div className="flex flex-col" id="comparisons">
469
+ <nav className="p-4 flex flex-row justify-between bg-white sticky top-0">
470
+ <div hidden={snapshots.length <= 0} className="flex gap-4 items-center flex-1">
471
+ <button onClick={PrevSnapshot} className="bg-highlight p-1 rounded">
472
+ <i className="fa fa-arrow-up"></i>
473
+ </button>
474
+ <button onClick={NextSnapshot} className="bg-highlight p-1 rounded">
475
+ <i className="fa fa-arrow-down"></i>
476
+ </button>
477
+ <h6 hidden={snapshots.length <= 0} className="p-2" >{activeSnapshot ? activeSnapshot.index + 1 : ''} / {snapshots.length}</h6>
478
+ <h6 hidden={snapshots.length <= 0} className="font-bold truncate flex-1 max-w-md">{activeSnapshot ? activeSnapshot.snapshot.name : ''}</h6>
479
+ </div>
480
+ <div className="flex gap-2 items-center w-fit">
481
+ <div onClick={() => SetDiffOverlay(!diffOverlay)} className={`${diffOverlay ? "bg-diff" : "bg-highlight"} p-2 cursor-pointer rounded`} >
482
+ <DiffIcon />
483
+ </div>
484
+ <div>
485
+ <BrowserSegments onChange={(browser) => SetFilter({ activeBrowser: browser })} activeBrowser={filters.activeBrowser} browsers={activeSnapshot ? activeSnapshot.browsers : []} />
486
+ </div>
487
+ <div>
488
+ <Picker onChange={(value) => SetFilter({ activeWidth: Number(value) })} value={filters.activeWidth} items={activeSnapshot ? activeSnapshot.widths[filters.activeBrowser] || [] : []} />
489
+ </div>
490
+ <div>
491
+ <Picker onChange={(value) => SetFilter({ activeDevice: value })} value={filters.activeDevice} items={activeSnapshot ? activeSnapshot.devices : []} />
492
+ </div>
493
+ </div>
494
+ </nav>
495
+ <div className="grid grid-cols-2">
496
+ {activeSnapshot && activeSnapshot.snapshot['enable-layout'] ? <div className="flex justify-center items-center gap-4 col-span-2">
497
+ <LayoutDiff /> Layout Diff
498
+ </div> : null}
499
+ {activeComparison ? <div className="flex justify-center items-center gap-4 col-span-2" > <DiffIcon />{`${(activeComparison['diff-ratio'] * 100).toFixed(4)}% Change`}</div> : null}
500
+ <div className="text-center font-bold" >Base Image</div>
501
+ <div className="text-center font-bold">Comparison Image</div>
502
+ </div>
503
+ <div className="mt-2 grid grid-cols-2 flex-1 gap-1 p-4" >
504
+ <div className="border-secondary" >
505
+ <BaseImage src={getImage(activeComparison, 'base')} alt="" loading="lazy" />
506
+ </div>
507
+
508
+ <div onClick={() => SetDiffOverlay(!diffOverlay)} className="border-secondary relative cursor-pointer" >
509
+ <HeadImage
510
+ diffOverlay={diffOverlay}
511
+ headSrc={getImage(activeComparison, 'head')}
512
+ diffSrc={getImage(activeComparison, 'diff')}
513
+ />
514
+ </div>
515
+ </div>
516
+ </div>
517
+ </div>
518
+ );
519
+ }
520
+ ReactDOM.render(<App />, document.getElementById('root'));
521
+
522
+ </script>
523
+ </body>
524
+
525
+ </html>