anshdadhich commited on
Commit
c17dcf7
Β·
verified Β·
1 Parent(s): fe0bbbc

Upload fastsearch-tauri/ui/app.js

Browse files
Files changed (1) hide show
  1. fastsearch-tauri/ui/app.js +44 -100
fastsearch-tauri/ui/app.js CHANGED
@@ -1,9 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  // ── DOM Elements ───────────────────────────────────────────────────
2
  const searchInput = document.getElementById('searchInput');
3
  const loadingSpinner = document.getElementById('loadingSpinner');
4
  const resultsContainer = document.getElementById('resultsContainer');
5
  const resultsList = document.getElementById('resultsList');
6
- const resultsFooter = document.getElementById('resultsFooter');
7
  const footerStats = document.getElementById('footerStats');
8
  const emptyState = document.getElementById('emptyState');
9
  const noResults = document.getElementById('noResults');
@@ -17,16 +29,10 @@ let totalIndexed = 0;
17
  // ── Icon helpers ───────────────────────────────────────────────────
18
  function getFileIcon(name, isDir) {
19
  if (isDir) return 'πŸ“';
20
-
21
  const ext = name.split('.').pop()?.toLowerCase() || '';
22
-
23
  const iconMap = {
24
- exe: 'βš™οΈ',
25
- lnk: 'πŸ”—',
26
- pdf: 'πŸ“„',
27
- doc: 'πŸ“', docx: 'πŸ“',
28
- xls: 'πŸ“Š', xlsx: 'πŸ“Š',
29
- ppt: 'πŸ“Š', pptx: 'πŸ“Š',
30
  jpg: 'πŸ–ΌοΈ', jpeg: 'πŸ–ΌοΈ', png: 'πŸ–ΌοΈ', gif: 'πŸ–ΌοΈ', bmp: 'πŸ–ΌοΈ', svg: 'πŸ–ΌοΈ', webp: 'πŸ–ΌοΈ',
31
  mp4: '🎬', mov: '🎬', avi: '🎬', mkv: '🎬',
32
  mp3: '🎡', wav: '🎡', flac: '🎡', aac: '🎡',
@@ -38,7 +44,6 @@ function getFileIcon(name, isDir) {
38
  db: 'πŸ—„οΈ', sql: 'πŸ—„οΈ',
39
  ini: 'βš™οΈ', cfg: 'βš™οΈ', conf: 'βš™οΈ',
40
  };
41
-
42
  return iconMap[ext] || 'πŸ“„';
43
  }
44
 
@@ -65,29 +70,32 @@ function highlightMatch(name, query) {
65
  );
66
  }
67
 
 
 
 
 
 
 
68
  // ── Render results ────────────────────────────────────────────────
69
  function renderResults() {
70
  resultsList.innerHTML = '';
71
-
72
  results.forEach((item, idx) => {
73
  const el = document.createElement('div');
74
  el.className = 'result-item' + (idx === selectedIndex ? ' selected' : '');
75
  el.dataset.index = idx;
76
  el.addEventListener('click', () => selectAndOpen(idx));
77
  el.addEventListener('dblclick', () => openItem(idx, true));
78
-
79
  const iconClass = getFileClass(item.name, item.is_dir);
80
  const icon = getFileIcon(item.name, item.is_dir);
81
-
82
  const query = searchInput.value.trim();
83
  const highlightedName = highlightMatch(item.name, query);
84
-
85
- // Format path - shorten if too long
86
  let displayPath = item.path;
87
  if (displayPath.length > 80) {
88
  displayPath = '...' + displayPath.slice(-77);
89
  }
90
-
91
  el.innerHTML = `
92
  <div class="result-icon ${iconClass}">${icon}</div>
93
  <div class="result-info">
@@ -96,25 +104,15 @@ function renderResults() {
96
  </div>
97
  <div class="result-meta">${item.is_dir ? 'DIR' : 'FILE'}</div>
98
  `;
99
-
100
  resultsList.appendChild(el);
101
  });
102
  }
103
 
104
- function escapeHtml(text) {
105
- const div = document.createElement('div');
106
- div.textContent = text;
107
- return div.innerHTML;
108
- }
109
-
110
- // ── Update selection ────────────────────────────────────────────
111
  function updateSelection() {
112
  const items = resultsList.querySelectorAll('.result-item');
113
  items.forEach((el, idx) => {
114
  el.classList.toggle('selected', idx === selectedIndex);
115
  });
116
-
117
- // Scroll into view
118
  if (selectedIndex >= 0) {
119
  const selected = items[selectedIndex];
120
  if (selected) {
@@ -145,47 +143,32 @@ function selectAndOpen(index) {
145
  function openItem(index, reveal) {
146
  if (index < 0 || index >= results.length) return;
147
  const item = results[index];
148
-
149
- if (typeof window.__TAURI__ !== 'undefined') {
150
  if (reveal || item.is_dir) {
151
- window.__TAURI__.invoke('cmd_open_folder', { path: item.path });
152
  } else {
153
- window.__TAURI__.invoke('cmd_open_file', { path: item.path });
154
  }
155
  } else {
156
- // Fallback for dev
157
  console.log('Open:', item.path);
158
  }
159
-
160
- // Hide window after opening
161
  setTimeout(() => {
162
- if (typeof window.__TAURI__ !== 'undefined') {
163
- window.__TAURI__.invoke('cmd_toggle_window');
164
- }
165
  }, 100);
166
  }
167
 
168
  // ── Perform search ───────────────────────────────────────────────
169
  function performSearch(query) {
170
- if (!query) {
171
- showEmptyState();
172
- return;
173
- }
174
-
175
  loadingSpinner.classList.add('active');
176
-
177
- if (typeof window.__TAURI__ !== 'undefined') {
178
- window.__TAURI__.invoke('cmd_search', {
179
- request: {
180
- query: query,
181
- limit: 50,
182
- case_sensitive: false
183
- }
184
  }).then(response => {
185
  loadingSpinner.classList.remove('active');
186
  results = response.results || [];
187
  totalIndexed = response.total_indexed;
188
-
189
  if (results.length > 0) {
190
  showResults();
191
  selectedIndex = 0;
@@ -199,7 +182,6 @@ function performSearch(query) {
199
  console.error('Search error:', err);
200
  });
201
  } else {
202
- // Dev mode - show empty
203
  loadingSpinner.classList.remove('active');
204
  showEmptyState();
205
  }
@@ -235,67 +217,35 @@ function updateFooter(elapsedMs) {
235
  // ── Search input handler ─────────────────────────────────────────
236
  searchInput.addEventListener('input', (e) => {
237
  const query = e.target.value.trim();
238
-
239
  clearTimeout(searchTimeout);
240
-
241
- if (!query) {
242
- showEmptyState();
243
- return;
244
- }
245
-
246
- searchTimeout = setTimeout(() => {
247
- performSearch(query);
248
- }, 50); // 50ms debounce for instant feel
249
  });
250
 
251
  // ── Keyboard navigation ──────────────────────────────────────────
252
  document.addEventListener('keydown', (e) => {
253
- // ESC - close window
254
  if (e.key === 'Escape') {
255
- if (typeof window.__TAURI__ !== 'undefined') {
256
- window.__TAURI__.invoke('cmd_toggle_window');
257
- }
258
  return;
259
  }
260
-
261
- // Arrow down - next result
262
- if (e.key === 'ArrowDown') {
263
- e.preventDefault();
264
- selectNext();
265
- return;
266
- }
267
-
268
- // Arrow up - previous result
269
- if (e.key === 'ArrowUp') {
270
- e.preventDefault();
271
- selectPrev();
272
- return;
273
- }
274
-
275
- // Enter - open selected
276
  if (e.key === 'Enter') {
277
  e.preventDefault();
278
- const reveal = e.ctrlKey || e.metaKey;
279
- openItem(selectedIndex, reveal);
280
  return;
281
  }
282
-
283
- // Tab - focus search input (if not focused)
284
  if (e.key === 'Tab') {
285
  e.preventDefault();
286
  searchInput.focus();
287
  return;
288
  }
289
-
290
- // Any printable key - focus search input
291
  if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
292
- if (document.activeElement !== searchInput) {
293
- searchInput.focus();
294
- }
295
  }
296
  });
297
 
298
- // ── Focus search on show ──────────────────────────────────────────
299
  function resetSearch() {
300
  searchInput.value = '';
301
  searchInput.focus();
@@ -303,15 +253,9 @@ function resetSearch() {
303
  }
304
 
305
  // ── Listen for toggle event from Rust ────────────────────────────
306
- if (typeof window.__TAURI__ !== 'undefined') {
307
- window.__TAURI__.event.listen('toggle-search', () => {
308
- resetSearch();
309
- });
310
-
311
- // Load stats on startup
312
- window.__TAURI__.invoke('cmd_get_stats').then(stats => {
313
- totalIndexed = stats.total_indexed;
314
- });
315
  }
316
 
317
  // Focus input on load
 
1
+ // ── Tauri v2 API wrapper ───────────────────────────────────────────
2
+ const TAURI = typeof window.__TAURI__ !== 'undefined' ? window.__TAURI__ : null;
3
+
4
+ // v2: __TAURI__.core.invoke | v1 fallback: __TAURI__.invoke
5
+ const invoke = (TAURI && TAURI.core && TAURI.core.invoke)
6
+ ? TAURI.core.invoke
7
+ : (TAURI ? TAURI.invoke : null);
8
+
9
+ // v2: __TAURI__.event.listen | v1 fallback: __TAURI__.event.listen
10
+ const listen = (TAURI && TAURI.event && TAURI.event.listen)
11
+ ? TAURI.event.listen
12
+ : (TAURI && TAURI.event ? TAURI.event.listen : null);
13
+
14
  // ── DOM Elements ───────────────────────────────────────────────────
15
  const searchInput = document.getElementById('searchInput');
16
  const loadingSpinner = document.getElementById('loadingSpinner');
17
  const resultsContainer = document.getElementById('resultsContainer');
18
  const resultsList = document.getElementById('resultsList');
 
19
  const footerStats = document.getElementById('footerStats');
20
  const emptyState = document.getElementById('emptyState');
21
  const noResults = document.getElementById('noResults');
 
29
  // ── Icon helpers ───────────────────────────────────────────────────
30
  function getFileIcon(name, isDir) {
31
  if (isDir) return 'πŸ“';
 
32
  const ext = name.split('.').pop()?.toLowerCase() || '';
 
33
  const iconMap = {
34
+ exe: 'βš™οΈ', lnk: 'πŸ”—', pdf: 'πŸ“„',
35
+ doc: 'πŸ“', docx: 'πŸ“', xls: 'πŸ“Š', xlsx: 'πŸ“Š', ppt: 'πŸ“Š', pptx: 'πŸ“Š',
 
 
 
 
36
  jpg: 'πŸ–ΌοΈ', jpeg: 'πŸ–ΌοΈ', png: 'πŸ–ΌοΈ', gif: 'πŸ–ΌοΈ', bmp: 'πŸ–ΌοΈ', svg: 'πŸ–ΌοΈ', webp: 'πŸ–ΌοΈ',
37
  mp4: '🎬', mov: '🎬', avi: '🎬', mkv: '🎬',
38
  mp3: '🎡', wav: '🎡', flac: '🎡', aac: '🎡',
 
44
  db: 'πŸ—„οΈ', sql: 'πŸ—„οΈ',
45
  ini: 'βš™οΈ', cfg: 'βš™οΈ', conf: 'βš™οΈ',
46
  };
 
47
  return iconMap[ext] || 'πŸ“„';
48
  }
49
 
 
70
  );
71
  }
72
 
73
+ function escapeHtml(text) {
74
+ const div = document.createElement('div');
75
+ div.textContent = text;
76
+ return div.innerHTML;
77
+ }
78
+
79
  // ── Render results ────────────────────────────────────────────────
80
  function renderResults() {
81
  resultsList.innerHTML = '';
 
82
  results.forEach((item, idx) => {
83
  const el = document.createElement('div');
84
  el.className = 'result-item' + (idx === selectedIndex ? ' selected' : '');
85
  el.dataset.index = idx;
86
  el.addEventListener('click', () => selectAndOpen(idx));
87
  el.addEventListener('dblclick', () => openItem(idx, true));
88
+
89
  const iconClass = getFileClass(item.name, item.is_dir);
90
  const icon = getFileIcon(item.name, item.is_dir);
 
91
  const query = searchInput.value.trim();
92
  const highlightedName = highlightMatch(item.name, query);
93
+
 
94
  let displayPath = item.path;
95
  if (displayPath.length > 80) {
96
  displayPath = '...' + displayPath.slice(-77);
97
  }
98
+
99
  el.innerHTML = `
100
  <div class="result-icon ${iconClass}">${icon}</div>
101
  <div class="result-info">
 
104
  </div>
105
  <div class="result-meta">${item.is_dir ? 'DIR' : 'FILE'}</div>
106
  `;
 
107
  resultsList.appendChild(el);
108
  });
109
  }
110
 
 
 
 
 
 
 
 
111
  function updateSelection() {
112
  const items = resultsList.querySelectorAll('.result-item');
113
  items.forEach((el, idx) => {
114
  el.classList.toggle('selected', idx === selectedIndex);
115
  });
 
 
116
  if (selectedIndex >= 0) {
117
  const selected = items[selectedIndex];
118
  if (selected) {
 
143
  function openItem(index, reveal) {
144
  if (index < 0 || index >= results.length) return;
145
  const item = results[index];
146
+ if (invoke) {
 
147
  if (reveal || item.is_dir) {
148
+ invoke('cmd_open_folder', { path: item.path });
149
  } else {
150
+ invoke('cmd_open_file', { path: item.path });
151
  }
152
  } else {
 
153
  console.log('Open:', item.path);
154
  }
 
 
155
  setTimeout(() => {
156
+ if (invoke) invoke('cmd_toggle_window');
 
 
157
  }, 100);
158
  }
159
 
160
  // ── Perform search ───────────────────────────────────────────────
161
  function performSearch(query) {
162
+ if (!query) { showEmptyState(); return; }
 
 
 
 
163
  loadingSpinner.classList.add('active');
164
+
165
+ if (invoke) {
166
+ invoke('cmd_search', {
167
+ request: { query: query, limit: 50, case_sensitive: false }
 
 
 
 
168
  }).then(response => {
169
  loadingSpinner.classList.remove('active');
170
  results = response.results || [];
171
  totalIndexed = response.total_indexed;
 
172
  if (results.length > 0) {
173
  showResults();
174
  selectedIndex = 0;
 
182
  console.error('Search error:', err);
183
  });
184
  } else {
 
185
  loadingSpinner.classList.remove('active');
186
  showEmptyState();
187
  }
 
217
  // ── Search input handler ─────────────────────────────────────────
218
  searchInput.addEventListener('input', (e) => {
219
  const query = e.target.value.trim();
 
220
  clearTimeout(searchTimeout);
221
+ if (!query) { showEmptyState(); return; }
222
+ searchTimeout = setTimeout(() => performSearch(query), 50);
 
 
 
 
 
 
 
223
  });
224
 
225
  // ── Keyboard navigation ──────────────────────────────────────────
226
  document.addEventListener('keydown', (e) => {
 
227
  if (e.key === 'Escape') {
228
+ if (invoke) invoke('cmd_toggle_window');
 
 
229
  return;
230
  }
231
+ if (e.key === 'ArrowDown') { e.preventDefault(); selectNext(); return; }
232
+ if (e.key === 'ArrowUp') { e.preventDefault(); selectPrev(); return; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  if (e.key === 'Enter') {
234
  e.preventDefault();
235
+ openItem(selectedIndex, e.ctrlKey || e.metaKey);
 
236
  return;
237
  }
 
 
238
  if (e.key === 'Tab') {
239
  e.preventDefault();
240
  searchInput.focus();
241
  return;
242
  }
 
 
243
  if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
244
+ if (document.activeElement !== searchInput) searchInput.focus();
 
 
245
  }
246
  });
247
 
248
+ // ── Reset & focus ──────────────────────────────────────────────────
249
  function resetSearch() {
250
  searchInput.value = '';
251
  searchInput.focus();
 
253
  }
254
 
255
  // ── Listen for toggle event from Rust ────────────────────────────
256
+ if (listen) {
257
+ listen('toggle-search', () => resetSearch());
258
+ invoke('cmd_get_stats').then(stats => { totalIndexed = stats.total_indexed; });
 
 
 
 
 
 
259
  }
260
 
261
  // Focus input on load