desktop: components/apps/browser — simple web browser with URL bar, navigation, iframe sandbox

This commit is contained in:
Butterfly Dev 2026-04-07 03:38:14 +00:00
parent 6beb93b16f
commit eea8197ece
3 changed files with 165 additions and 0 deletions

View File

@ -0,0 +1,29 @@
<div class="browser">
<!-- Navigation bar -->
<div class="browser-toolbar">
<button class="nav-btn" (click)="refresh()" title="Refresh"></button>
<div class="url-bar">
<input
class="url-input"
type="text"
[value]="inputUrl()"
(keydown)="onUrlKeydown($event)"
placeholder="Enter URL…"
/>
<button class="go-btn" (click)="navigate()">Go</button>
</div>
</div>
<!-- Content -->
<div class="browser-content">
<iframe
[src]="url()"
class="browser-frame"
sandbox="allow-same-origin allow-scripts allow-forms"
(load)="onLoad()"
></iframe>
@if (isLoading()) {
<div class="browser-loading">Loading…</div>
}
</div>
</div>

View File

@ -0,0 +1,96 @@
:host {
display: block;
width: 100%;
height: 100%;
}
.browser {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #1e1e1e;
}
.browser-toolbar {
display: flex;
align-items: center;
height: 40px;
padding: 0 8px;
background: #2d2d2d;
border-bottom: 1px solid #404040;
gap: 6px;
}
.nav-btn {
width: 32px;
height: 30px;
border: none;
background: transparent;
color: #ccc;
font-size: 18px;
cursor: pointer;
border-radius: 4px;
&:hover {
background: rgba(255, 255, 255, 0.08);
}
}
.url-bar {
flex: 1;
display: flex;
height: 30px;
border: 1px solid #555;
border-radius: 6px;
background: #383838;
overflow: hidden;
&:focus-within {
border-color: #0078d4;
}
}
.url-input {
flex: 1;
border: none;
outline: none;
background: transparent;
color: #e0e0e0;
font-size: 13px;
padding: 0 10px;
}
.go-btn {
width: 50px;
border: none;
background: #0078d4;
color: #fff;
font-size: 12px;
cursor: pointer;
&:hover {
background: #1a86d9;
}
}
.browser-content {
flex: 1;
position: relative;
}
.browser-frame {
width: 100%;
height: 100%;
border: none;
background: #fff;
}
.browser-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #888;
font-size: 14px;
}

View File

@ -0,0 +1,40 @@
import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-browser',
standalone: true,
imports: [CommonModule],
templateUrl: './browser.component.html',
styleUrl: './browser.component.scss',
})
export class BrowserComponent {
readonly url = signal('https://www.wikipedia.org');
readonly inputUrl = signal('https://www.wikipedia.org');
readonly isLoading = signal(false);
navigate(): void {
let target = this.inputUrl().trim();
if (!target.startsWith('http://') && !target.startsWith('https://')) {
target = 'https://' + target;
}
this.inputUrl.set(target);
this.url.set(target);
}
onUrlKeydown(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.navigate();
}
}
onLoad(): void {
this.isLoading.set(false);
}
refresh(): void {
this.isLoading.set(true);
this.url.set(this.url() + ' '); // Force iframe reload
setTimeout(() => this.url.set(this.inputUrl()), 50);
}
}