Rewild Your Web
web
browser
dweb
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3import {
4 LitElement,
5 html,
6 css,
7} from "//shared.localhost:8888/third_party/lit/lit-all.min.js";
8import { SearchController } from "//shared.localhost:8888/search/controller.js";
9
10export class UrlBarOverlay extends LitElement {
11 static properties = {
12 open: { type: Boolean, reflect: true },
13 url: { type: String },
14 groups: { state: true },
15 onNavigate: { type: Function },
16 onSelectWebView: { type: Function },
17 };
18
19 static styles = css`
20 @import url(//system.localhost:8888/url_bar_overlay.css);
21 `;
22
23 constructor() {
24 super();
25 this.open = false;
26 this.url = "";
27 this.groups = [];
28 this.onNavigate = null;
29 this.onSelectWebView = null;
30
31 this.controller = new SearchController({
32 onNavigate: (url) => this.navigateTo(url),
33 onSelectWebView: (windowId, webviewId) =>
34 this.selectWebView(windowId, webviewId),
35 onResultsChanged: (results, groups) => {
36 this.groups = groups;
37 },
38 });
39 }
40
41 updated(changedProperties) {
42 if (changedProperties.has("open") && this.open) {
43 // Focus and select the input when opening
44 this.updateComplete.then(() => {
45 const input = this.shadowRoot.querySelector(".search-input");
46 if (input) {
47 input.focus();
48 input.select();
49 }
50 });
51 // Initial query with current URL
52 this.controller.queryImmediate(this.url);
53 }
54 }
55
56 handleBackdropClick() {
57 this.close();
58 }
59
60 handleKeydown(e) {
61 if (e.key === "Escape") {
62 e.preventDefault();
63 this.close();
64 } else if (e.key === "Enter") {
65 e.preventDefault();
66 const input = this.shadowRoot.querySelector(".search-input");
67 this.controller.handleSubmit(input?.value || "");
68 }
69 }
70
71 handleInput(e) {
72 this.controller.query(e.target.value);
73 }
74
75 handleResultClick(e, result) {
76 e.preventDefault();
77 this.controller.handleResultClick(result);
78 }
79
80 navigateTo(url) {
81 if (this.onNavigate) {
82 this.onNavigate(url);
83 }
84 this.close();
85 }
86
87 selectWebView(windowId, webviewId) {
88 if (this.onSelectWebView) {
89 this.onSelectWebView(windowId, webviewId);
90 }
91 this.close();
92 }
93
94 close() {
95 this.open = false;
96 this.groups = [];
97 this.controller.clear();
98 this.dispatchEvent(new CustomEvent("close"));
99 }
100
101 render() {
102 return html`
103 <div class="backdrop" @click=${this.handleBackdropClick}></div>
104 <div class="overlay" @keydown=${this.handleKeydown}>
105 <div class="search-header">
106 <img src="//system.localhost:8888/logo.png" alt="Logo" class="logo" />
107 <input
108 type="text"
109 class="search-input"
110 .value=${this.url}
111 placeholder="Search or enter URL…"
112 @input=${this.handleInput}
113 />
114 </div>
115 <div class="results-container">
116 <div class="results-list">
117 ${this.groups.map(
118 (group) => html`
119 <div class="result-group">
120 <div class="result-group-icon">
121 ${group.providerIcon
122 ? html`<lucide-icon
123 name="${group.providerIcon}"
124 ></lucide-icon>`
125 : null}
126 </div>
127 <div class="result-group-items">
128 ${group.items.map(
129 (result) => html`
130 <div
131 class="result-item"
132 data-kind=${result.kind}
133 @click=${(e) => this.handleResultClick(e, result)}
134 >
135 ${result.kind === "link" || result.kind === "webview"
136 ? html`<span class="result-link"
137 >${result.value.title}</span
138 >`
139 : html`<span class="result-text"
140 >${result.value}</span
141 >`}
142 </div>
143 `
144 )}
145 </div>
146 </div>
147 `
148 )}
149 </div>
150 </div>
151 </div>
152 `;
153 }
154}
155
156customElements.define("url-bar-overlay", UrlBarOverlay);