main
html 936 lines 31.3 KB
Raw
1 <html>
2 <head>
3 <script type="module">
4 import { store } from "/plugins/_time_travel/webui/time-travel-store.js";
5 </script>
6 </head>
7 <body>
8 <div class="time-travel-panel" x-data x-create="$store.timeTravel.onMount($el, xAttrs($el) || {})" x-destroy="$store.timeTravel.cleanup()">
9 <template x-if="$store.timeTravel">
10 <div class="time-travel-shell">
11 <div class="time-travel-toolbar">
12 <div class="time-travel-workspace-picker" :title="$store.timeTravel.workspacePath || $store.timeTravel.workspaceOptionLabel($store.timeTravel.selectedWorkspace())">
13 <x-icon aria-hidden="true" name="folder_open"></x-icon>
14 <select
15 aria-label="Workspace"
16 :value="$store.timeTravel.selectedWorkspaceId"
17 @change="$store.timeTravel.selectWorkspace($event.target.value)"
18 :disabled="$store.timeTravel.workspaceLoading || $store.timeTravel.loading || $store.timeTravel.busy"
19 >
20 <option value="" x-show="$store.timeTravel.workspaces.length === 0">Workspace</option>
21 <template x-for="workspace in $store.timeTravel.workspaces" :key="workspace.id">
22 <option
23 :value="workspace.id"
24 :selected="workspace.id === $store.timeTravel.selectedWorkspaceId"
25 x-text="$store.timeTravel.workspaceOptionLabel(workspace)"
26 ></option>
27 </template>
28 </select>
29 </div>
30 <span class="time-travel-spacer"></span>
31 <button type="button" class="time-travel-icon-button" title="Snapshot" aria-label="Snapshot" @click="$store.timeTravel.manualSnapshot()" :disabled="$store.timeTravel.busy || $store.timeTravel.loading || $store.timeTravel.isLocked()">
32 <x-icon name="add_a_photo"></x-icon>
33 </button>
34 <button type="button" class="time-travel-icon-button" title="Refresh" aria-label="Refresh" @click="$store.timeTravel.refresh({ keepSelection: true, reloadWorkspaces: true })" :disabled="$store.timeTravel.loading">
35 <x-icon :class="{ spinning: $store.timeTravel.loading }" name="refresh"></x-icon>
36 </button>
37 </div>
38
39 <div class="time-travel-status" x-show="$store.timeTravel.loading || $store.timeTravel.error || $store.timeTravel.busy" style="display: none;">
40 <x-icon :class="{ spinning: $store.timeTravel.loading || $store.timeTravel.busy }" :name="$store.timeTravel.error ? 'error' : 'progress_activity'"></x-icon>
41 <span x-text="$store.timeTravel.error || ($store.timeTravel.busy ? 'Working...' : 'Loading history...')"></span>
42 </div>
43
44 <template x-if="$store.timeTravel.isLocked() && !$store.timeTravel.loading">
45 <div class="time-travel-locked">
46 <x-icon name="lock"></x-icon>
47 <strong>Unavailable</strong>
48 <span x-text="$store.timeTravel.payload?.workspace?.error || 'Time Travel is available only inside /a0/usr.'"></span>
49 </div>
50 </template>
51
52 <template x-if="!$store.timeTravel.isLocked()">
53 <div class="time-travel-body">
54 <aside class="time-travel-timeline">
55 <div class="time-travel-filter">
56 <x-icon name="filter_list"></x-icon>
57 <input
58 type="search"
59 placeholder="Filter files"
60 x-model="$store.timeTravel.fileFilter"
61 @input="$store.timeTravel.scheduleFilterRefresh()"
62 />
63 </div>
64
65 <div class="time-travel-rows">
66 <template x-for="row in $store.timeTravel.timelineRows()" :key="row.key">
67 <button
68 type="button"
69 class="time-travel-row"
70 :class="{ 'is-active': $store.timeTravel.selectedHash === row.key, 'is-present': row.kind === 'present', 'is-current': row.is_current }"
71 @click="$store.timeTravel.selectRow(row)"
72 :title="row.message"
73 >
74 <span class="time-travel-row-mark">
75 <x-icon :name="row.kind === 'present' ? (row.dirty ? 'edit_note' : 'check_circle') : (row.is_current ? 'radio_button_checked' : 'commit')"></x-icon>
76 </span>
77 <span class="time-travel-row-main">
78 <span class="time-travel-row-title" x-text="row.message"></span>
79 <span class="time-travel-row-meta" x-text="$store.timeTravel.rowMeta(row)"></span>
80 </span>
81 <span class="time-travel-row-count" x-text="row.files?.length || 0"></span>
82 </button>
83 </template>
84 </div>
85
86 <button type="button" class="time-travel-load-more" x-show="$store.timeTravel.payload?.has_more" style="display: none;" @click="$store.timeTravel.loadMore()" :disabled="$store.timeTravel.loading">
87 <x-icon name="expand_more"></x-icon>
88 <span>More</span>
89 </button>
90 </aside>
91
92 <main class="time-travel-detail">
93 <div class="time-travel-detail-header">
94 <div class="time-travel-detail-title">
95 <strong x-text="$store.timeTravel.selectedRow()?.message || 'History'"></strong>
96 <span x-text="$store.timeTravel.rowMeta($store.timeTravel.selectedRow())"></span>
97 </div>
98 <div class="time-travel-actions" x-show="$store.timeTravel.selectedCommit()" style="display: none;">
99 <button type="button" class="time-travel-tool-button" title="Travel" @click="$store.timeTravel.openPreview('travel')" :disabled="$store.timeTravel.selectedCommit()?.is_current || $store.timeTravel.busy">
100 <x-icon name="move_down"></x-icon>
101 <span>Travel</span>
102 </button>
103 <button type="button" class="time-travel-tool-button" title="Revert" @click="$store.timeTravel.openPreview('revert')" :disabled="$store.timeTravel.busy">
104 <x-icon name="undo"></x-icon>
105 <span>Revert</span>
106 </button>
107 </div>
108 </div>
109
110 <div class="time-travel-detail-grid">
111 <section class="time-travel-files">
112 <template x-if="$store.timeTravel.selectedFiles().length === 0">
113 <div class="time-travel-empty">
114 <x-icon name="hourglass_empty"></x-icon>
115 <span>No file changes</span>
116 </div>
117 </template>
118 <template x-for="file in $store.timeTravel.selectedFiles()" :key="$store.timeTravel.fileKey(file)">
119 <button
120 type="button"
121 class="time-travel-file-row"
122 :class="{ 'is-active': $store.timeTravel.fileKey(file) === $store.timeTravel.selectedPath }"
123 @click="$store.timeTravel.selectFile(file)"
124 :title="$store.timeTravel.fileTitle(file)"
125 >
126 <span class="time-travel-file-name" x-text="$store.timeTravel.fileTitle(file)"></span>
127 <span class="time-travel-file-status" x-text="$store.timeTravel.statusLabel(file)"></span>
128 <span class="time-travel-file-counts">
129 <span class="diff-add" x-text="$store.timeTravel.formatSigned(file.additions, '+')"></span>
130 <span class="diff-del" x-text="$store.timeTravel.formatSigned(file.deletions, '-')"></span>
131 </span>
132 </button>
133 </template>
134 </section>
135
136 <section class="time-travel-diff">
137 <div class="time-travel-diff-toolbar">
138 <span class="time-travel-diff-path" :title="$store.timeTravel.fileTitle($store.timeTravel.selectedFile())" x-text="$store.timeTravel.fileTitle($store.timeTravel.selectedFile()) || 'Diff'"></span>
139 <span class="time-travel-spacer"></span>
140 <button type="button" class="time-travel-icon-button is-small" title="Open containing folder" aria-label="Open containing folder" @click="$store.timeTravel.openContainingFolder($store.timeTravel.selectedFile())" :disabled="!$store.timeTravel.selectedFile()">
141 <x-icon name="folder_open"></x-icon>
142 </button>
143 <button type="button" class="time-travel-icon-button is-small" title="Copy path" aria-label="Copy path" @click="$store.timeTravel.copyPath($store.timeTravel.selectedFile())" :disabled="!$store.timeTravel.selectedFile()">
144 <x-icon name="content_copy"></x-icon>
145 </button>
146 </div>
147
148 <div class="time-travel-diff-status" x-show="$store.timeTravel.diffLoading || $store.timeTravel.diffError" style="display: none;">
149 <x-icon :class="{ spinning: $store.timeTravel.diffLoading }" :name="$store.timeTravel.diffError ? 'error' : 'progress_activity'"></x-icon>
150 <span x-text="$store.timeTravel.diffError || 'Loading diff...'"></span>
151 </div>
152
153 <template x-if="$store.timeTravel.selectedDiff?.binary">
154 <div class="time-travel-empty">
155 <x-icon name="data_object"></x-icon>
156 <span>Binary file changed</span>
157 </div>
158 </template>
159
160 <template x-if="$store.timeTravel.selectedDiff?.too_large && !$store.timeTravel.selectedDiff?.binary">
161 <div class="time-travel-empty">
162 <x-icon name="text_snippet"></x-icon>
163 <span>Diff exceeds the 1 MB render limit</span>
164 </div>
165 </template>
166
167 <template x-if="$store.timeTravel.selectedDiff?.patch && !$store.timeTravel.selectedDiff?.binary">
168 <div class="time-travel-code" role="table" aria-label="Unified diff">
169 <template x-for="line in $store.timeTravel.patchLines()" :key="line.id">
170 <div class="time-travel-line" :class="`is-${line.type}`" role="row">
171 <span class="time-travel-line-marker" x-text="line.type === 'add' ? '+' : (line.type === 'del' ? '-' : '')"></span>
172 <code x-text="line.text"></code>
173 </div>
174 </template>
175 </div>
176 </template>
177 </section>
178 </div>
179 </main>
180 </div>
181 </template>
182
183 <div class="time-travel-preview-backdrop" x-show="$store.timeTravel.previewOpen" style="display: none;">
184 <section class="time-travel-preview" role="dialog" aria-modal="true" aria-label="Time Travel preview">
185 <header>
186 <x-icon :name="$store.timeTravel.preview?.operation === 'travel' ? 'move_down' : 'undo'"></x-icon>
187 <strong x-text="$store.timeTravel.preview?.operation === 'travel' ? 'Travel Preview' : 'Revert Preview'"></strong>
188 <button type="button" class="time-travel-icon-button is-small" title="Close" aria-label="Close" @click="$store.timeTravel.closePreview()" :disabled="$store.timeTravel.busy">
189 <x-icon name="close"></x-icon>
190 </button>
191 </header>
192 <div class="time-travel-preview-body">
193 <div class="time-travel-preview-status" x-show="$store.timeTravel.previewLoading || $store.timeTravel.previewError" style="display: none;">
194 <x-icon :class="{ spinning: $store.timeTravel.previewLoading }" :name="$store.timeTravel.previewError ? 'error' : 'progress_activity'"></x-icon>
195 <span x-text="$store.timeTravel.previewError || 'Building preview...'"></span>
196 </div>
197 <div class="time-travel-preview-summary">
198 <span x-text="$store.timeTravel.preview?.short_hash || ''"></span>
199 <span class="time-travel-dot"></span>
200 <span x-text="`${$store.timeTravel.preview?.files?.length || 0} affected files`"></span>
201 </div>
202 <div class="time-travel-preview-files">
203 <template x-for="file in ($store.timeTravel.preview?.files || []).slice(0, 80)" :key="$store.timeTravel.fileKey(file)">
204 <div class="time-travel-preview-file">
205 <span x-text="$store.timeTravel.fileTitle(file)"></span>
206 <span x-text="$store.timeTravel.statusLabel(file)"></span>
207 </div>
208 </template>
209 </div>
210 <details x-show="$store.timeTravel.previewTechnicalDetails" style="display: none;">
211 <summary>Details</summary>
212 <pre x-text="$store.timeTravel.previewTechnicalDetails"></pre>
213 </details>
214 </div>
215 <footer>
216 <button type="button" class="time-travel-tool-button" @click="$store.timeTravel.closePreview()" :disabled="$store.timeTravel.busy">
217 <x-icon name="close"></x-icon>
218 <span>Cancel</span>
219 </button>
220 <button type="button" class="time-travel-tool-button is-primary" @click="$store.timeTravel.confirmPreview()" :disabled="Boolean($store.timeTravel.busy || $store.timeTravel.previewLoading || $store.timeTravel.previewError)">
221 <x-icon :class="{ spinning: $store.timeTravel.busy }" :name="$store.timeTravel.busy ? 'progress_activity' : ($store.timeTravel.preview?.operation === 'travel' ? 'move_down' : 'undo')"></x-icon>
222 <span x-text="$store.timeTravel.preview?.operation === 'travel' ? 'Travel' : 'Revert'"></span>
223 </button>
224 </footer>
225 </section>
226 </div>
227 </div>
228 </template>
229 </div>
230
231 <style>
232 .time-travel-panel,
233 .time-travel-shell {
234 display: flex;
235 flex: 1 1 auto;
236 flex-direction: column;
237 width: 100%;
238 height: 100%;
239 min-width: 0;
240 min-height: 0;
241 background: color-mix(in srgb, var(--color-background) 95%, #000 5%);
242 color: var(--color-text);
243 }
244
245 .time-travel-panel {
246 container-type: inline-size;
247 }
248
249 .modal-inner.time-travel-modal {
250 box-sizing: border-box;
251 container-type: inline-size;
252 width: min(86vw, 1240px);
253 height: min(88vh, 920px);
254 min-width: min(360px, calc(100vw - 16px));
255 min-height: min(520px, calc(100vh - 16px));
256 max-width: calc(100vw - 16px);
257 max-height: calc(100vh - 16px);
258 border: 1px solid color-mix(in srgb, var(--color-border) 75%, transparent);
259 border-radius: 7px;
260 box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
261 background: color-mix(in srgb, var(--color-background) 94%, #000 6%);
262 }
263
264 .modal-inner.time-travel-modal .modal-header {
265 min-height: 42px;
266 padding: 0.45rem 0.75rem 0.45rem 1rem;
267 background: color-mix(in srgb, var(--color-background) 92%, #000 8%);
268 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
269 }
270
271 .modal-inner.time-travel-modal .modal-scroll,
272 .modal-inner.time-travel-modal .modal-bd.time-travel-modal-body,
273 .modal-inner.time-travel-modal .modal-bd.time-travel-modal-body > x-component,
274 .modal-inner.time-travel-modal .modal-bd.time-travel-modal-body > div[x-data] {
275 display: flex;
276 flex: 1 1 auto;
277 width: 100%;
278 height: 100%;
279 min-height: 0;
280 overflow: hidden;
281 padding: 0;
282 }
283
284 .time-travel-toolbar,
285 .time-travel-status,
286 .time-travel-filter,
287 .time-travel-actions,
288 .time-travel-tool-button,
289 .time-travel-diff-toolbar,
290 .time-travel-diff-status,
291 .time-travel-file-counts,
292 .time-travel-preview header,
293 .time-travel-preview footer,
294 .time-travel-preview-status,
295 .time-travel-preview-summary {
296 display: flex;
297 align-items: center;
298 }
299
300 .time-travel-toolbar {
301 gap: 8px;
302 min-height: 44px;
303 padding: var(--spacing-xs) var(--spacing-md);
304 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 64%, transparent);
305 background: color-mix(in srgb, var(--color-background) 91%, #000 9%);
306 }
307
308 .time-travel-workspace-picker {
309 display: flex;
310 align-items: center;
311 gap: 6px;
312 flex: 0 1 520px;
313 min-width: 0;
314 max-width: 48%;
315 height: 32px;
316 padding: 0 8px;
317 border: 1px solid color-mix(in srgb, var(--color-border) 58%, transparent);
318 border-radius: 7px;
319 background: color-mix(in srgb, var(--color-panel) 70%, transparent);
320 }
321
322 .time-travel-workspace-picker .material-symbols-outlined {
323 flex: 0 0 auto;
324 color: var(--color-text-muted);
325 font-size: 17px;
326 }
327
328 .time-travel-workspace-picker select {
329 width: 100%;
330 min-width: 0;
331 border: 0;
332 outline: 0;
333 background: transparent;
334 color: var(--color-text);
335 cursor: pointer;
336 font-size: 0.76rem;
337 overflow: hidden;
338 text-overflow: ellipsis;
339 white-space: nowrap;
340 }
341
342 .time-travel-workspace-picker select:disabled {
343 cursor: not-allowed;
344 opacity: 0.65;
345 }
346
347 .time-travel-workspace-picker option {
348 background: var(--color-background);
349 color: var(--color-text);
350 }
351
352 .time-travel-spacer {
353 flex: 1 1 auto;
354 min-width: 8px;
355 }
356
357 .time-travel-icon-button,
358 .time-travel-tool-button,
359 .time-travel-load-more {
360 appearance: none;
361 border: 1px solid color-mix(in srgb, var(--color-border) 64%, transparent);
362 border-radius: 7px;
363 background: color-mix(in srgb, var(--color-panel) 80%, transparent);
364 color: var(--color-text);
365 font: inherit;
366 cursor: pointer;
367 }
368
369 .time-travel-icon-button {
370 display: inline-flex;
371 align-items: center;
372 justify-content: center;
373 width: 32px;
374 height: 32px;
375 min-width: 32px;
376 padding: 0;
377 }
378
379 .time-travel-icon-button.is-small {
380 width: 28px;
381 height: 28px;
382 min-width: 28px;
383 }
384
385 .time-travel-tool-button {
386 gap: 5px;
387 min-height: 30px;
388 padding: 4px 9px;
389 font-size: 0.76rem;
390 }
391
392 .time-travel-tool-button.is-primary {
393 border-color: color-mix(in srgb, var(--color-primary) 42%, var(--color-border));
394 background: color-mix(in srgb, var(--color-primary) 22%, var(--color-panel));
395 }
396
397 .time-travel-icon-button:hover:not(:disabled),
398 .time-travel-tool-button:hover:not(:disabled),
399 .time-travel-load-more:hover:not(:disabled) {
400 background: color-mix(in srgb, var(--color-background-hover) 70%, transparent);
401 border-color: color-mix(in srgb, var(--color-primary) 28%, var(--color-border));
402 }
403
404 .time-travel-icon-button:disabled,
405 .time-travel-tool-button:disabled,
406 .time-travel-load-more:disabled {
407 cursor: not-allowed;
408 opacity: 0.45;
409 }
410
411 .time-travel-icon-button .material-symbols-outlined,
412 .time-travel-tool-button .material-symbols-outlined,
413 .time-travel-load-more .material-symbols-outlined {
414 font-size: 17px;
415 }
416
417 .time-travel-status,
418 .time-travel-diff-status,
419 .time-travel-preview-status {
420 gap: 8px;
421 min-height: 34px;
422 padding: 6px 11px;
423 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 44%, transparent);
424 color: var(--color-text-muted);
425 font-size: 0.82rem;
426 }
427
428 .time-travel-body {
429 display: grid;
430 flex: 1 1 auto;
431 grid-template-columns: minmax(210px, 0.42fr) minmax(0, 1fr);
432 min-width: 0;
433 min-height: 0;
434 }
435
436 .time-travel-timeline {
437 display: flex;
438 flex-direction: column;
439 min-width: 0;
440 min-height: 0;
441 border-right: 1px solid color-mix(in srgb, var(--color-border) 58%, transparent);
442 background: color-mix(in srgb, var(--color-panel) 48%, transparent);
443 }
444
445 .time-travel-filter {
446 gap: 6px;
447 min-height: 40px;
448 padding: 7px 9px;
449 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 42%, transparent);
450 }
451
452 .time-travel-filter .material-symbols-outlined {
453 font-size: 17px;
454 color: var(--color-text-muted);
455 }
456
457 .time-travel-filter input {
458 width: 100%;
459 min-width: 0;
460 height: 28px;
461 border: 1px solid color-mix(in srgb, var(--color-border) 60%, transparent);
462 border-radius: 7px;
463 background: color-mix(in srgb, var(--color-background) 72%, transparent);
464 color: var(--color-text);
465 font-size: 0.78rem;
466 padding: 0 8px;
467 }
468
469 .time-travel-rows {
470 display: flex;
471 flex: 1 1 auto;
472 flex-direction: column;
473 min-width: 0;
474 min-height: 0;
475 overflow: auto;
476 padding: 7px;
477 }
478
479 .time-travel-row {
480 display: grid;
481 grid-template-columns: 24px minmax(0, 1fr) auto;
482 align-items: center;
483 gap: 7px;
484 width: 100%;
485 min-height: 50px;
486 padding: 6px 7px;
487 border: 1px solid transparent;
488 border-radius: 7px;
489 background: transparent;
490 color: var(--color-text);
491 font: inherit;
492 text-align: left;
493 cursor: pointer;
494 }
495
496 .time-travel-row:hover,
497 .time-travel-row.is-active {
498 border-color: color-mix(in srgb, var(--color-primary) 25%, var(--color-border));
499 background: color-mix(in srgb, var(--color-background-hover) 56%, transparent);
500 }
501
502 .time-travel-row-mark .material-symbols-outlined {
503 font-size: 18px;
504 color: var(--color-text-muted);
505 }
506
507 .time-travel-row.is-current .time-travel-row-mark .material-symbols-outlined {
508 color: var(--color-primary);
509 }
510
511 .time-travel-row-main {
512 display: grid;
513 min-width: 0;
514 gap: 2px;
515 }
516
517 .time-travel-row-title,
518 .time-travel-row-meta,
519 .time-travel-file-name,
520 .time-travel-diff-path {
521 min-width: 0;
522 overflow: hidden;
523 text-overflow: ellipsis;
524 white-space: nowrap;
525 }
526
527 .time-travel-row-title {
528 font-size: 0.79rem;
529 font-weight: 650;
530 }
531
532 .time-travel-row-meta {
533 color: var(--color-text-muted);
534 font-family: var(--font-family-code);
535 font-size: 0.69rem;
536 }
537
538 .time-travel-row-count {
539 min-width: 24px;
540 padding: 2px 6px;
541 border-radius: 999px;
542 background: color-mix(in srgb, var(--color-background) 70%, transparent);
543 color: var(--color-text-muted);
544 text-align: center;
545 font-size: 0.7rem;
546 }
547
548 .time-travel-load-more {
549 display: flex;
550 align-items: center;
551 justify-content: center;
552 gap: 5px;
553 min-height: 34px;
554 margin: 7px;
555 font-size: 0.76rem;
556 }
557
558 .time-travel-detail {
559 display: flex;
560 flex-direction: column;
561 min-width: 0;
562 min-height: 0;
563 }
564
565 .time-travel-detail-header {
566 display: grid;
567 grid-template-columns: minmax(0, 1fr) auto;
568 gap: 8px;
569 align-items: center;
570 min-height: 48px;
571 padding: 8px 10px;
572 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 50%, transparent);
573 }
574
575 .time-travel-detail-title {
576 display: grid;
577 min-width: 0;
578 gap: 2px;
579 }
580
581 .time-travel-detail-title strong,
582 .time-travel-detail-title span {
583 min-width: 0;
584 overflow: hidden;
585 text-overflow: ellipsis;
586 white-space: nowrap;
587 }
588
589 .time-travel-detail-title strong {
590 font-size: 0.88rem;
591 }
592
593 .time-travel-detail-title span {
594 color: var(--color-text-muted);
595 font-size: 0.72rem;
596 }
597
598 .time-travel-actions {
599 gap: 6px;
600 }
601
602 .time-travel-detail-grid {
603 display: grid;
604 flex: 1 1 auto;
605 grid-template-columns: minmax(190px, 0.36fr) minmax(0, 1fr);
606 min-width: 0;
607 min-height: 0;
608 }
609
610 .time-travel-files {
611 min-width: 0;
612 min-height: 0;
613 overflow: auto;
614 border-right: 1px solid color-mix(in srgb, var(--color-border) 45%, transparent);
615 }
616
617 .time-travel-file-row {
618 display: grid;
619 grid-template-columns: minmax(0, 1fr) auto;
620 gap: 4px 7px;
621 width: 100%;
622 min-height: 46px;
623 padding: 7px 9px;
624 border: 0;
625 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 26%, transparent);
626 background: transparent;
627 color: var(--color-text);
628 font: inherit;
629 text-align: left;
630 cursor: pointer;
631 }
632
633 .time-travel-file-row:hover,
634 .time-travel-file-row.is-active {
635 background: color-mix(in srgb, var(--color-background-hover) 54%, transparent);
636 }
637
638 .time-travel-file-name {
639 font-family: var(--font-family-code);
640 font-size: 0.74rem;
641 grid-column: 1 / -1;
642 }
643
644 .time-travel-file-status {
645 color: var(--color-text-muted);
646 font-size: 0.68rem;
647 text-transform: capitalize;
648 }
649
650 .time-travel-file-counts {
651 gap: 5px;
652 justify-content: end;
653 font-family: var(--font-family-code);
654 font-size: 0.7rem;
655 }
656
657 .time-travel-diff {
658 display: flex;
659 flex-direction: column;
660 min-width: 0;
661 min-height: 0;
662 overflow: hidden;
663 }
664
665 .time-travel-diff-toolbar {
666 gap: 6px;
667 min-height: 38px;
668 padding: 5px 8px;
669 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 42%, transparent);
670 background: color-mix(in srgb, var(--color-panel) 44%, transparent);
671 }
672
673 .time-travel-diff-path {
674 font-family: var(--font-family-code);
675 font-size: 0.74rem;
676 font-weight: 650;
677 }
678
679 .time-travel-code {
680 flex: 1 1 auto;
681 min-width: 0;
682 min-height: 0;
683 overflow: auto;
684 padding: 4px 0;
685 background: color-mix(in srgb, var(--color-background) 91%, #000 9%);
686 font-family: var(--font-family-code);
687 font-size: 0.73rem;
688 line-height: 1.45;
689 }
690
691 .time-travel-line {
692 display: grid;
693 grid-template-columns: 24px minmax(0, 1fr);
694 min-width: max-content;
695 }
696
697 .time-travel-line-marker {
698 position: sticky;
699 left: 0;
700 z-index: 1;
701 min-height: 1.45em;
702 padding-right: 5px;
703 background: inherit;
704 color: var(--color-text-muted);
705 text-align: right;
706 user-select: none;
707 }
708
709 .time-travel-line code {
710 display: block;
711 min-height: 1.45em;
712 padding: 0 10px 0 6px;
713 color: inherit;
714 font: inherit;
715 white-space: pre;
716 }
717
718 .time-travel-line.is-add {
719 background: rgba(39, 174, 96, 0.16);
720 color: color-mix(in srgb, #8df0b0 78%, var(--color-text));
721 }
722
723 .time-travel-line.is-del {
724 background: rgba(231, 76, 60, 0.17);
725 color: color-mix(in srgb, #ffaaa2 78%, var(--color-text));
726 }
727
728 .time-travel-line.is-hunk {
729 background: rgba(99, 102, 241, 0.16);
730 color: color-mix(in srgb, #b9c3ff 80%, var(--color-text));
731 }
732
733 .time-travel-line.is-meta,
734 .time-travel-line.is-note {
735 color: var(--color-text-muted);
736 }
737
738 .time-travel-empty,
739 .time-travel-locked {
740 display: grid;
741 flex: 1 1 auto;
742 place-items: center;
743 align-content: center;
744 gap: 7px;
745 min-width: 0;
746 padding: 24px;
747 color: var(--color-text-muted);
748 text-align: center;
749 }
750
751 .time-travel-empty .material-symbols-outlined,
752 .time-travel-locked .material-symbols-outlined {
753 font-size: 28px;
754 }
755
756 .time-travel-locked span:last-child {
757 max-width: 520px;
758 overflow-wrap: anywhere;
759 font-size: 0.8rem;
760 }
761
762 .diff-add {
763 color: #31c48d;
764 }
765
766 .diff-del {
767 color: #f05252;
768 }
769
770 .time-travel-dot {
771 width: 4px;
772 height: 4px;
773 flex: 0 0 auto;
774 border-radius: 999px;
775 background: color-mix(in srgb, var(--color-text-muted) 50%, transparent);
776 }
777
778 .time-travel-preview-backdrop {
779 position: absolute;
780 inset: 0;
781 z-index: 20;
782 display: grid;
783 place-items: center;
784 padding: 16px;
785 background: rgba(0, 0, 0, 0.34);
786 }
787
788 .time-travel-preview {
789 display: flex;
790 flex-direction: column;
791 width: min(640px, 100%);
792 max-height: min(680px, 100%);
793 min-height: 260px;
794 border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
795 border-radius: 8px;
796 background: color-mix(in srgb, var(--color-background) 95%, #000 5%);
797 box-shadow: 0 18px 46px rgba(0, 0, 0, 0.36);
798 overflow: hidden;
799 }
800
801 .time-travel-preview header,
802 .time-travel-preview footer {
803 gap: 8px;
804 min-height: 44px;
805 padding: 8px 10px;
806 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 54%, transparent);
807 }
808
809 .time-travel-preview footer {
810 justify-content: flex-end;
811 border-top: 1px solid color-mix(in srgb, var(--color-border) 54%, transparent);
812 border-bottom: 0;
813 }
814
815 .time-travel-preview header strong {
816 flex: 1 1 auto;
817 min-width: 0;
818 font-size: 0.9rem;
819 }
820
821 .time-travel-preview-body {
822 display: flex;
823 flex: 1 1 auto;
824 flex-direction: column;
825 min-width: 0;
826 min-height: 0;
827 overflow: auto;
828 }
829
830 .time-travel-preview-summary {
831 gap: 8px;
832 padding: 9px 11px;
833 color: var(--color-text-muted);
834 font-family: var(--font-family-code);
835 font-size: 0.74rem;
836 }
837
838 .time-travel-preview-files {
839 display: grid;
840 gap: 4px;
841 padding: 0 11px 11px;
842 }
843
844 .time-travel-preview-file {
845 display: grid;
846 grid-template-columns: minmax(0, 1fr) auto;
847 gap: 8px;
848 min-height: 30px;
849 align-items: center;
850 border: 1px solid color-mix(in srgb, var(--color-border) 34%, transparent);
851 border-radius: 7px;
852 padding: 4px 7px;
853 background: color-mix(in srgb, var(--color-panel) 62%, transparent);
854 font-size: 0.73rem;
855 }
856
857 .time-travel-preview-file span:first-child {
858 min-width: 0;
859 overflow: hidden;
860 text-overflow: ellipsis;
861 white-space: nowrap;
862 font-family: var(--font-family-code);
863 }
864
865 .time-travel-preview-file span:last-child {
866 color: var(--color-text-muted);
867 text-transform: capitalize;
868 }
869
870 .time-travel-preview details {
871 margin: 0 11px 11px;
872 color: var(--color-text-muted);
873 font-size: 0.76rem;
874 }
875
876 .time-travel-preview pre {
877 max-height: 160px;
878 overflow: auto;
879 padding: 8px;
880 border-radius: 7px;
881 background: color-mix(in srgb, var(--color-background) 86%, #000 14%);
882 white-space: pre-wrap;
883 }
884
885 .time-travel-panel .spinning {
886 display: inline-block;
887 animation: time-travel-spin 0.8s linear infinite;
888 }
889
890 @keyframes time-travel-spin {
891 to { transform: rotate(360deg); }
892 }
893
894 @container (max-width: 720px) {
895 .time-travel-body,
896 .time-travel-detail-grid {
897 grid-template-columns: minmax(0, 1fr);
898 }
899
900 .time-travel-timeline,
901 .time-travel-files {
902 max-height: 34vh;
903 border-right: 0;
904 border-bottom: 1px solid color-mix(in srgb, var(--color-border) 48%, transparent);
905 }
906
907 .time-travel-detail-header {
908 grid-template-columns: minmax(0, 1fr);
909 }
910
911 .time-travel-actions {
912 justify-content: flex-start;
913 }
914
915 .time-travel-workspace-picker {
916 flex: 1 1 160px;
917 max-width: none;
918 }
919 }
920
921 @container (max-width: 520px) {
922 .time-travel-workspace-picker {
923 flex-basis: 120px;
924 }
925
926 .time-travel-tool-button span:last-child {
927 display: none;
928 }
929
930 .time-travel-row {
931 min-height: 46px;
932 }
933 }
934 </style>
935 </body>
936 </html>