Rewild Your Web
web
browser
dweb
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3// DOM elements
4const bookmarksGrid = document.getElementById("bookmarks-grid");
5const resultsArea = document.getElementById("results-area");
6const resultsList = document.getElementById("results-list");
7const searchInput = document.getElementById("search-input");
8const widgetsArea = document.getElementById("widgets-area");
9
10// Load widgets from JSON
11async function loadWidgets() {
12 try {
13 const response = await fetch("resources/widgets.json");
14 const widgets = await response.json();
15 renderWidgets(widgets);
16 } catch (e) {
17 console.log("[Homescreen] No widgets configured");
18 }
19}
20
21// Render widgets as iframes
22function renderWidgets(widgets) {
23 for (const widget of widgets) {
24 const iframe = document.createElement("iframe");
25 iframe.className = "widget-frame";
26 iframe.src = widget.url;
27 iframe.style.width = widget.width || "100%";
28 iframe.style.height = widget.height || "100px";
29 widgetsArea.appendChild(iframe);
30 }
31}
32
33// Load bookmarks from JSON
34async function loadBookmarks() {
35 try {
36 const response = await fetch("resources/bookmarks.json");
37 const bookmarks = await response.json();
38 renderBookmarks(bookmarks);
39 } catch (e) {
40 console.error("[Homescreen] Failed to load bookmarks:", e);
41 }
42}
43
44// Render bookmarks to the grid
45function renderBookmarks(bookmarks) {
46 bookmarksGrid.innerHTML = "";
47
48 for (const bookmark of bookmarks) {
49 const item = document.createElement("div");
50 item.className = "bookmark-item";
51 item.innerHTML = `
52 <div class="bookmark-icon">
53 <img src="resources/${bookmark.icon}" alt="" onerror="this.style.display='none'" />
54 </div>
55 <span class="bookmark-title">${bookmark.title}</span>
56 `;
57 // TODO: replace with <a target="_blank">
58 item.addEventListener("click", () => {
59 navigate(bookmark.url);
60 });
61 bookmarksGrid.appendChild(item);
62 }
63}
64
65// Reset search UI to initial state
66function resetSearchState() {
67 searchInput.value = "";
68 searchInput.blur();
69 document.body.classList.remove("searching");
70 controller.clear();
71}
72
73// Navigate to a URL
74function navigate(url) {
75 resetSearchState();
76 window.open(url, "_blank");
77}
78
79class Controller {
80 constructor() {
81 this.inner = null;
82 }
83
84 async ensureController() {
85 if (this.inner) {
86 return;
87 }
88
89 let mod = await import("//shared.localhost:8888/search/controller.js");
90 this.inner = new mod.SearchController({
91 onNavigate: navigate,
92 onResultsChanged: renderResults,
93 debounceDelay: 150,
94 });
95 }
96
97 async handleResultClick(result) {
98 await this.ensureController();
99 this.inner.handleResultClick(result);
100 }
101
102 async handleSubmit(data) {
103 await this.ensureController();
104 this.inner.handleSubmit(data);
105 }
106
107 async query(query) {
108 await this.ensureController();
109 this.inner.query(query);
110 }
111
112 async clear() {
113 await this.ensureController();
114 this.inner.clear();
115 }
116}
117
118// Initialize search controller
119const controller = new Controller();
120
121// Render search results (adapted from new_view.js)
122function renderResults(results, groups) {
123 resultsList.innerHTML = "";
124
125 if (results.length === 0) {
126 return;
127 }
128
129 for (const group of groups) {
130 const groupDiv = document.createElement("div");
131 groupDiv.className = "result-group";
132
133 // Icon column
134 const iconDiv = document.createElement("div");
135 iconDiv.className = "result-group-icon";
136 if (group.providerIcon) {
137 const icon = document.createElement("lucide-icon");
138 icon.setAttribute("name", group.providerIcon);
139 iconDiv.appendChild(icon);
140 }
141 groupDiv.appendChild(iconDiv);
142
143 // Items column
144 const itemsDiv = document.createElement("div");
145 itemsDiv.className = "result-group-items";
146
147 for (const result of group.items) {
148 const itemDiv = document.createElement("div");
149 itemDiv.className = "result-item";
150 itemDiv.dataset.kind = result.kind;
151
152 if (result.kind === "link" || result.kind === "webview") {
153 const link = document.createElement("a");
154 link.href = result.kind === "link" ? result.value.url : "#";
155 link.className = "result-link";
156 link.textContent = result.value.title;
157 link.addEventListener("click", (e) => {
158 e.preventDefault();
159 controller.handleResultClick(result);
160 });
161 itemDiv.appendChild(link);
162 } else if (result.kind === "text") {
163 const text = document.createElement("span");
164 text.className = "result-text";
165 text.textContent = result.value;
166 itemDiv.appendChild(text);
167 }
168
169 itemsDiv.appendChild(itemDiv);
170 }
171
172 groupDiv.appendChild(itemsDiv);
173 resultsList.appendChild(groupDiv);
174 }
175}
176
177// Event listeners
178searchInput.addEventListener("input", () => {
179 const query = searchInput.value.trim();
180 if (query) {
181 document.body.classList.add("searching");
182 controller.query(query);
183 } else {
184 document.body.classList.remove("searching");
185 controller.clear();
186 }
187});
188
189searchInput.addEventListener("keydown", (e) => {
190 if (e.key === "Enter") {
191 e.preventDefault();
192 controller.handleSubmit(searchInput.value);
193 }
194});
195
196// Initialize
197loadWidgets();
198loadBookmarks();