Skip to content

Commit 4d0e94c

Browse files
committed
Tweaks to hotkeys, focus, and other things.
1 parent 4fc37e1 commit 4d0e94c

11 files changed

Lines changed: 77 additions & 115 deletions

frontend/src/app/active-request.logic.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.

frontend/src/app/app.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
(onRevealInFinder)="revealInFinder($event)"
2121
(onCloseOtherTabs)="closeOtherTabs($event)"
2222
(onImportPostman)="importPostmanCollection()"
23-
(onImportBruno)="importBrunoCollection()">
23+
(onImportBruno)="importBrunoCollection()"
24+
(onOutlineClick)="toggleOutlinePanel()"
25+
(onSearchRequestsClick)="showCommandPalette = !showCommandPalette">>
2426
</app-header>
2527

2628
<main class="rr-app__main">

frontend/src/app/app.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
ConsoleDrawerComponent,
1616
UpdateNotificationComponent,
1717
ScriptSnippetModalComponent,
18-
SecretsModalComponent
1918
} from './components';
2019
import { OutlinePanelComponent } from './components/outline-panel/outline-panel.component';
2120
import { CommandPaletteComponent } from './components/command-palette/command-palette.component';
@@ -238,6 +237,7 @@ export class AppComponent implements OnInit, OnDestroy {
238237
canCancel: boolean;
239238
type: 'single' | 'chain' | 'load';
240239
startedAt: number;
240+
processedUrl?: string;
241241
} | null = null;
242242
isCancellingActiveRequest = false;
243243
downloadProgress: { downloaded: number; total: number } | null = null;
@@ -554,7 +554,7 @@ export class AppComponent implements OnInit, OnDestroy {
554554
hydrateText(request.url, variables, envName, (text, env) => this.secretService.replaceSecrets(text, env))
555555
.then(resolved => {
556556
if (this.activeRequestInfo?.id === capturedId) {
557-
this.activeRequestInfo = { ...this.activeRequestInfo, processedUrl: resolved };
557+
this.activeRequestInfo = { ...this.activeRequestInfo!, processedUrl: resolved };
558558
}
559559
})
560560
.catch(() => {});

frontend/src/app/components/editor/editor.lint.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export function computeDiagnostics(view: EditorView, deps: LintDeps): Diagnostic
168168
// These don't depend on the parsed request model and help catch structural mistakes early.
169169
{
170170
const doc = view.state.doc;
171+
const seenNames = new Map<string, { from: number; to: number }>();
171172
tree.iterate({
172173
enter: (node) => {
173174
if (node.type.name !== 'RequestBlock') return;
@@ -362,6 +363,18 @@ export function computeDiagnostics(view: EditorView, deps: LintDeps): Diagnostic
362363
severity: 'warning',
363364
message: 'Missing @name value'
364365
});
366+
} else {
367+
const prev = seenNames.get(nameArg);
368+
if (prev) {
369+
diagnostics.push({
370+
from: child.from,
371+
to: child.to,
372+
severity: 'error',
373+
message: `Duplicate @name "${nameArg}"`
374+
});
375+
} else {
376+
seenNames.set(nameArg, { from: child.from, to: child.to });
377+
}
365378
}
366379
}
367380

frontend/src/app/components/header/header.component.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,28 @@
173173
<span>Secrets</span>
174174
</button>
175175

176+
<button
177+
type="button"
178+
class="rr-btn btn-ambient"
179+
(click)="onOutlineClick.emit()"
180+
title="Outline (⌘/Ctrl+Shift+O)"
181+
aria-label="Toggle outline panel"
182+
>
183+
<svg xmlns="http://www.w3.org/2000/svg" class="rr-btn__icon rr-icon--primary" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" /><path d="M9 6h11" /><path d="M9 12h11" /><path d="M9 18h11" /><path d="M5 6v.01" /><path d="M5 12v.01" /><path d="M5 18v.01" /></svg>
184+
<span>Outline</span>
185+
</button>
186+
187+
<button
188+
type="button"
189+
class="rr-btn btn-ambient"
190+
(click)="onSearchRequestsClick.emit()"
191+
title="Search Requests (⌘/Ctrl+P)"
192+
aria-label="Search requests"
193+
>
194+
<svg xmlns="http://www.w3.org/2000/svg" class="rr-btn__icon rr-icon--primary" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" /><circle cx="10" cy="10" r="7" /><path d="M21 21l-6 -6" /></svg>
195+
<span>Search</span>
196+
</button>
197+
176198
<button
177199
type="button"
178200
class="rr-btn btn-ambient"

frontend/src/app/components/header/header.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export class HeaderComponent {
3737
onCloseOtherTabs = output<number>();
3838
onImportPostman = output<void>();
3939
onImportBruno = output<void>();
40+
onOutlineClick = output<void>();
41+
onSearchRequestsClick = output<void>();
4042

4143
draggingIndex: number | null = null;
4244
dragOverIndex: number | null = null;

frontend/src/app/components/outline-panel/outline-panel.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ <h5 class="outline-drawer__title-text">Outline</h5>
2222

2323
<div class="outline-drawer__search">
2424
<input type="text"
25+
#filterInput
2526
class="outline-drawer__search-input"
2627
placeholder="Filter requests..."
27-
[(ngModel)]="filterQuery"
28+
[ngModel]="filterQuery()"
29+
(ngModelChange)="filterQuery.set($event)"
2830
autocomplete="off"
2931
spellcheck="false" />
3032
</div>

frontend/src/app/components/outline-panel/outline-panel.component.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, input, output, computed } from '@angular/core';
1+
import { Component, input, output, computed, effect, ElementRef, viewChild, signal } from '@angular/core';
22
import { FormsModule } from '@angular/forms';
33
import type { Request } from '../../models/http.models';
44
import { buildOutlineEntries, groupOutlineEntries, filterOutlineEntries, type OutlineGroup } from './outline-panel.logic';
@@ -18,18 +18,28 @@ export class OutlinePanelComponent {
1818
onClose = output<void>();
1919
onRequestSelect = output<number>();
2020

21-
filterQuery = '';
21+
filterQuery = signal('');
22+
filterInput = viewChild<ElementRef>('filterInput');
2223

2324
private allEntries = computed(() => buildOutlineEntries(this.requests()));
2425

2526
groups = computed<OutlineGroup[]>(() => {
26-
const filtered = filterOutlineEntries(this.allEntries(), this.filterQuery);
27+
const filtered = filterOutlineEntries(this.allEntries(), this.filterQuery());
2728
return groupOutlineEntries(filtered);
2829
});
2930

3031
requestCount = computed(() => this.requests().length);
3132

33+
constructor() {
34+
effect(() => {
35+
if (this.isOpen()) {
36+
setTimeout(() => this.filterInput()?.nativeElement.focus(), 0);
37+
}
38+
});
39+
}
40+
3241
selectRequest(index: number): void {
3342
this.onRequestSelect.emit(index);
43+
this.onClose.emit();
3444
}
3545
}

frontend/src/app/components/outline-panel/outline-panel.logic.spec.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ describe('outline-panel.logic', () => {
1616

1717
it('uses request name as label when available', () => {
1818
const entries = buildOutlineEntries([makeRequest({ name: 'GetUsers', method: 'GET' })]);
19-
expect(entries).toEqual([{ requestIndex: 0, method: 'GET', label: 'GetUsers', group: null }]);
19+
expect(entries).toEqual([{ requestIndex: 0, method: 'GET', label: 'GetUsers', url: 'https://example.com/users', group: null }]);
2020
});
2121

22-
it('extracts path from URL as fallback label', () => {
22+
it('uses full URL as fallback label', () => {
2323
const entries = buildOutlineEntries([makeRequest({ url: 'https://api.example.com/v1/users' })]);
24-
expect(entries[0].label).toBe('/v1/users');
24+
expect(entries[0].label).toBe('https://api.example.com/v1/users');
2525
});
2626

2727
it('uses full URL when no path extractable', () => {
@@ -31,7 +31,7 @@ describe('outline-panel.logic', () => {
3131

3232
it('handles template variables in URL', () => {
3333
const entries = buildOutlineEntries([makeRequest({ url: '{{baseUrl}}/users' })]);
34-
expect(entries[0].label).toBe('/users');
34+
expect(entries[0].label).toBe('{{baseUrl}}/users');
3535
});
3636

3737
it('preserves group from request', () => {
@@ -48,8 +48,8 @@ describe('outline-panel.logic', () => {
4848
describe('groupOutlineEntries', () => {
4949
it('puts ungrouped entries first', () => {
5050
const entries = [
51-
{ requestIndex: 0, method: 'GET', label: 'A', group: null },
52-
{ requestIndex: 1, method: 'POST', label: 'B', group: 'Auth' }
51+
{ requestIndex: 0, method: 'GET', label: 'A', url: '', group: null },
52+
{ requestIndex: 1, method: 'POST', label: 'B', url: '', group: 'Auth' }
5353
];
5454
const groups = groupOutlineEntries(entries);
5555
expect(groups.length).toBe(2);
@@ -60,8 +60,8 @@ describe('outline-panel.logic', () => {
6060

6161
it('groups multiple requests under same group', () => {
6262
const entries = [
63-
{ requestIndex: 0, method: 'POST', label: 'Login', group: 'Auth' },
64-
{ requestIndex: 1, method: 'POST', label: 'Logout', group: 'Auth' }
63+
{ requestIndex: 0, method: 'POST', label: 'Login', url: '', group: 'Auth' },
64+
{ requestIndex: 1, method: 'POST', label: 'Logout', url: '', group: 'Auth' }
6565
];
6666
const groups = groupOutlineEntries(entries);
6767
expect(groups.length).toBe(1);
@@ -75,9 +75,9 @@ describe('outline-panel.logic', () => {
7575

7676
describe('filterOutlineEntries', () => {
7777
const entries = [
78-
{ requestIndex: 0, method: 'GET', label: 'GetUsers', group: 'Users' },
79-
{ requestIndex: 1, method: 'POST', label: 'Login', group: 'Auth' },
80-
{ requestIndex: 2, method: 'DELETE', label: 'RemoveUser', group: null }
78+
{ requestIndex: 0, method: 'GET', label: 'GetUsers', url: 'https://example.com/users', group: 'Users' },
79+
{ requestIndex: 1, method: 'POST', label: 'Login', url: 'https://example.com/auth', group: 'Auth' },
80+
{ requestIndex: 2, method: 'DELETE', label: 'RemoveUser', url: 'https://example.com/users/1', group: null }
8181
];
8282

8383
it('returns all entries for empty query', () => {

frontend/src/app/components/outline-panel/outline-panel.logic.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface OutlineEntry {
44
requestIndex: number;
55
method: string;
66
label: string;
7+
url: string;
78
group: string | null;
89
}
910

@@ -16,7 +17,8 @@ export function buildOutlineEntries(requests: Request[]): OutlineEntry[] {
1617
return requests.map((r, i) => ({
1718
requestIndex: i,
1819
method: (r.method || 'GET').toUpperCase(),
19-
label: r.name || extractPathFromUrl(r.url) || r.url || '(unnamed)',
20+
label: r.name || r.url || '(unnamed)',
21+
url: r.url || '',
2022
group: r.group || null
2123
}));
2224
}
@@ -62,6 +64,7 @@ export function filterOutlineEntries(entries: OutlineEntry[], query: string): Ou
6264
return entries.filter(e =>
6365
e.label.toLowerCase().includes(q) ||
6466
e.method.toLowerCase().includes(q) ||
67+
e.url.toLowerCase().includes(q) ||
6568
(e.group || '').toLowerCase().includes(q)
6669
);
6770
}

0 commit comments

Comments
 (0)