Add search history and zoom controls to settings
Implemented search history tracking and display using localStorage, with options to clear history from the settings page. Replaced the display scale slider with interactive zoom controls and preset buttons, applying zoom changes immediately. Updated styles and HTML structure to support these new features.
This commit is contained in:
@@ -128,6 +128,38 @@ function addToSiteHistory(url) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search history management using localStorage
|
||||||
|
function getSearchHistory() {
|
||||||
|
try {
|
||||||
|
const history = localStorage.getItem('searchHistory');
|
||||||
|
return history ? JSON.parse(history) : [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading search history from localStorage:', err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToSearchHistory(searchQuery) {
|
||||||
|
try {
|
||||||
|
let history = getSearchHistory();
|
||||||
|
// Remove if already exists to avoid duplicates
|
||||||
|
history = history.filter(item => item !== searchQuery);
|
||||||
|
// Add to beginning
|
||||||
|
history.unshift(searchQuery);
|
||||||
|
// Keep only last 100 entries
|
||||||
|
if (history.length > 100) {
|
||||||
|
history = history.slice(0, 100);
|
||||||
|
}
|
||||||
|
localStorage.setItem('searchHistory', JSON.stringify(history));
|
||||||
|
// Also save to file via IPC for persistence
|
||||||
|
if (window.electronAPI && window.electronAPI.invoke) {
|
||||||
|
window.electronAPI.invoke('save-search-history', history);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error saving search history to localStorage:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Store current theme colors globally for use by renderTabs
|
// Store current theme colors globally for use by renderTabs
|
||||||
let currentThemeColors = null;
|
let currentThemeColors = null;
|
||||||
|
|
||||||
@@ -653,6 +685,7 @@ function performNavigation(input, originalInputForHistory) {
|
|||||||
const isInternal = input.startsWith('nebula://');
|
const isInternal = input.startsWith('nebula://');
|
||||||
const isLikelyUrl = hasProtocol || input.includes('.');
|
const isLikelyUrl = hasProtocol || input.includes('.');
|
||||||
let resolved;
|
let resolved;
|
||||||
|
let isSearch = false;
|
||||||
if (isFileProtocol) {
|
if (isFileProtocol) {
|
||||||
resolved = input;
|
resolved = input;
|
||||||
} else if (looksLikeLocalPath) {
|
} else if (looksLikeLocalPath) {
|
||||||
@@ -660,6 +693,9 @@ function performNavigation(input, originalInputForHistory) {
|
|||||||
if (/^[A-Za-z]:\//.test(p)) resolved = 'file:///' + encodeURI(p); else if (p.startsWith('/')) resolved = 'file://' + encodeURI(p); else resolved = 'file://' + encodeURI(p);
|
if (/^[A-Za-z]:\//.test(p)) resolved = 'file:///' + encodeURI(p); else if (p.startsWith('/')) resolved = 'file://' + encodeURI(p); else resolved = 'file://' + encodeURI(p);
|
||||||
} else if (!isInternal && !isLikelyUrl) {
|
} else if (!isInternal && !isLikelyUrl) {
|
||||||
resolved = `https://www.google.com/search?q=${encodeURIComponent(input)}`;
|
resolved = `https://www.google.com/search?q=${encodeURIComponent(input)}`;
|
||||||
|
isSearch = true;
|
||||||
|
// Save to search history
|
||||||
|
addToSearchHistory(input);
|
||||||
} else {
|
} else {
|
||||||
resolved = resolveInternalUrl(input);
|
resolved = resolveInternalUrl(input);
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-2
@@ -234,7 +234,7 @@ button:hover {
|
|||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: linear-gradient(135deg, var(--primary), var(--accent));
|
background: var(--primary);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid var(--primary);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -244,7 +244,7 @@ button:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.primary-btn:hover {
|
.primary-btn:hover {
|
||||||
background: linear-gradient(135deg, var(--primary-hover), var(--accent));
|
background: var(--primary-hover);
|
||||||
box-shadow: 0 4px 24px rgba(123, 46, 255, 0.25);
|
box-shadow: 0 4px 24px rgba(123, 46, 255, 0.25);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
@@ -372,6 +372,84 @@ button:hover {
|
|||||||
color: color-mix(in srgb, var(--text) 85%, transparent);
|
color: color-mix(in srgb, var(--text) 85%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Zoom controls */
|
||||||
|
.zoom-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-presets {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-preset-btn {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-preset-btn:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
border-color: var(--primary);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-preset-btn.active {
|
||||||
|
background: var(--primary);
|
||||||
|
border-color: var(--primary);
|
||||||
|
color: white;
|
||||||
|
box-shadow: var(--glow-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-preset-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
.settings-fieldset {
|
.settings-fieldset {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|||||||
+89
-15
@@ -10,7 +10,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container" role="application">
|
<div class="container" role="application">
|
||||||
<aside class="sidebar" aria-label="Settings categories">
|
<aside class="sidebar" aria-label="Settings categories">
|
||||||
<h1>⚙️ Settings</h1>
|
<h1>Settings</h1>
|
||||||
<nav class="tabs" role="tablist">
|
<nav class="tabs" role="tablist">
|
||||||
<button class="tab-link active" role="tab" aria-selected="true" aria-controls="panel-general" id="tab-general" data-tab="general">General</button>
|
<button class="tab-link active" role="tab" aria-selected="true" aria-controls="panel-general" id="tab-general" data-tab="general">General</button>
|
||||||
<button class="tab-link" role="tab" aria-selected="false" aria-controls="panel-appearance" id="tab-appearance" data-tab="appearance">Appearance</button>
|
<button class="tab-link" role="tab" aria-selected="false" aria-controls="panel-appearance" id="tab-appearance" data-tab="appearance">Appearance</button>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<p class="note">A controller-friendly UI designed for Steam Deck and handheld devices.</p>
|
<p class="note">A controller-friendly UI designed for Steam Deck and handheld devices.</p>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<button id="launch-bigpicture-btn" class="primary-btn">
|
<button id="launch-bigpicture-btn" class="primary-btn">
|
||||||
<span style="font-size: 18px;">🎮</span> Launch Big Picture Mode
|
<span style="font-size: 18px;"></span> Launch Big Picture Mode
|
||||||
</button>
|
</button>
|
||||||
<span id="bigpicture-status" class="note"></span>
|
<span id="bigpicture-status" class="note"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,10 +127,21 @@
|
|||||||
<!-- Display Scale -->
|
<!-- Display Scale -->
|
||||||
<div class="customization-group">
|
<div class="customization-group">
|
||||||
<h3>Display Scale</h3>
|
<h3>Display Scale</h3>
|
||||||
<p class="note">Adjust the default zoom level when opening the browser. Changes require a reload to take effect.</p>
|
<p class="note">Adjust the zoom level for this window. Changes apply immediately.</p>
|
||||||
<div class="range-row">
|
<div class="zoom-controls">
|
||||||
<input type="range" id="display-scale-slider" min="50" max="300" value="100" step="10">
|
<button class="zoom-btn" id="zoom-decrease" title="Decrease zoom">−</button>
|
||||||
<span id="display-scale-value" class="range-value">100%</span>
|
<span id="display-scale-value" class="zoom-value">100%</span>
|
||||||
|
<button class="zoom-btn" id="zoom-increase" title="Increase zoom">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="zoom-presets">
|
||||||
|
<button class="zoom-preset-btn" data-zoom="60">60%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="70">70%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="80">80%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="90">90%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="100">100%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="110">110%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="120">120%</button>
|
||||||
|
<button class="zoom-preset-btn" data-zoom="130">130%</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -221,7 +232,7 @@
|
|||||||
<ul id="site-history-list"></ul>
|
<ul id="site-history-list"></ul>
|
||||||
<div class="button-row" style="margin-top:10px;">
|
<div class="button-row" style="margin-top:10px;">
|
||||||
<button id="clear-site-history-btn">Clear Site History</button>
|
<button id="clear-site-history-btn">Clear Site History</button>
|
||||||
<button id="add-test-history-btn">Add Test History</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -380,6 +391,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get search history from localStorage in this webview context
|
||||||
|
function getSearchHistoryFromLocalStorage() {
|
||||||
|
try {
|
||||||
|
const history = localStorage.getItem('searchHistory');
|
||||||
|
console.log('[SETTINGS DEBUG] localStorage searchHistory:', history);
|
||||||
|
return history ? JSON.parse(history) : [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[SETTINGS DEBUG] Error reading search history from localStorage:', err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sync site history from main browser's localStorage to this webview's localStorage
|
// Sync site history from main browser's localStorage to this webview's localStorage
|
||||||
function syncSiteHistoryFromMain() {
|
function syncSiteHistoryFromMain() {
|
||||||
try {
|
try {
|
||||||
@@ -399,6 +422,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync search history from main browser's localStorage to this webview's localStorage
|
||||||
|
function syncSearchHistoryFromMain() {
|
||||||
|
try {
|
||||||
|
// Try to get the parent window's localStorage
|
||||||
|
if (window.parent && window.parent !== window) {
|
||||||
|
const parentHistory = window.parent.localStorage.getItem('searchHistory');
|
||||||
|
if (parentHistory) {
|
||||||
|
localStorage.setItem('searchHistory', parentHistory);
|
||||||
|
console.log('[SETTINGS DEBUG] Synced search history from parent window');
|
||||||
|
return JSON.parse(parentHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (err) {
|
||||||
|
console.log('[SETTINGS DEBUG] Could not sync search history from parent:', err.message);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function addTestHistory() {
|
function addTestHistory() {
|
||||||
const testSites = [
|
const testSites = [
|
||||||
'https://www.google.com',
|
'https://www.google.com',
|
||||||
@@ -436,8 +478,11 @@
|
|||||||
siteHistory = getSiteHistoryFromLocalStorage();
|
siteHistory = getSiteHistoryFromLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get search history via message passing to parent or direct file access
|
// Try to get search history from parent or localStorage
|
||||||
let searchHistory = [];
|
let searchHistory = syncSearchHistoryFromMain();
|
||||||
|
if (searchHistory.length === 0) {
|
||||||
|
searchHistory = getSearchHistoryFromLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
const searchList = document.getElementById('search-history-list');
|
const searchList = document.getElementById('search-history-list');
|
||||||
const siteList = document.getElementById('site-history-list');
|
const siteList = document.getElementById('site-history-list');
|
||||||
@@ -447,11 +492,22 @@
|
|||||||
siteList.innerHTML = '';
|
siteList.innerHTML = '';
|
||||||
|
|
||||||
// Populate search history
|
// Populate search history
|
||||||
const searchLi = document.createElement('li');
|
if (searchHistory && searchHistory.length > 0) {
|
||||||
searchLi.textContent = 'Search history not available in webview context';
|
console.log('[SETTINGS DEBUG] Displaying', searchHistory.length, 'search history items');
|
||||||
searchLi.style.fontStyle = 'italic';
|
searchHistory.forEach(query => {
|
||||||
searchLi.style.color = '#666';
|
const li = document.createElement('li');
|
||||||
searchList.appendChild(searchLi);
|
li.style.wordBreak = 'break-all';
|
||||||
|
li.textContent = query;
|
||||||
|
li.style.color = 'var(--text)';
|
||||||
|
searchList.appendChild(li);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const searchLi = document.createElement('li');
|
||||||
|
searchLi.textContent = 'No search history yet';
|
||||||
|
searchLi.style.fontStyle = 'italic';
|
||||||
|
searchLi.style.color = '#666';
|
||||||
|
searchList.appendChild(searchLi);
|
||||||
|
}
|
||||||
|
|
||||||
// Populate site history
|
// Populate site history
|
||||||
if (siteHistory && siteHistory.length > 0) {
|
if (siteHistory && siteHistory.length > 0) {
|
||||||
@@ -526,7 +582,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clearSearchHistory() {
|
async function clearSearchHistory() {
|
||||||
console.log('Search history clearing not available in webview context');
|
try {
|
||||||
|
// Clear from localStorage
|
||||||
|
localStorage.removeItem('searchHistory');
|
||||||
|
console.log('[SETTINGS DEBUG] Cleared search history from localStorage');
|
||||||
|
|
||||||
|
// Also clear in parent window if accessible
|
||||||
|
if (window.parent && window.parent !== window) {
|
||||||
|
try {
|
||||||
|
window.parent.localStorage.removeItem('searchHistory');
|
||||||
|
console.log('[SETTINGS DEBUG] Cleared search history from parent');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[SETTINGS DEBUG] Could not clear parent search history:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadHistories(); // Refresh the display
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error clearing search history:', err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load histories on page load
|
// Load histories on page load
|
||||||
|
|||||||
+66
-11
@@ -95,6 +95,9 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (clearSearchBtn) {
|
if (clearSearchBtn) {
|
||||||
clearSearchBtn.addEventListener('click', async () => {
|
clearSearchBtn.addEventListener('click', async () => {
|
||||||
try {
|
try {
|
||||||
|
// Clear from localStorage in this context
|
||||||
|
try { localStorage.removeItem('searchHistory'); } catch {}
|
||||||
|
|
||||||
if (ipc) { await ipc.invoke('clear-search-history'); }
|
if (ipc) { await ipc.invoke('clear-search-history'); }
|
||||||
showStatus('Search history cleared');
|
showStatus('Search history cleared');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -171,23 +174,75 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Display scale controls
|
// Display scale controls
|
||||||
try {
|
try {
|
||||||
const scaleSlider = document.getElementById('display-scale-slider');
|
|
||||||
const scaleValue = document.getElementById('display-scale-value');
|
const scaleValue = document.getElementById('display-scale-value');
|
||||||
|
const zoomDecrease = document.getElementById('zoom-decrease');
|
||||||
|
const zoomIncrease = document.getElementById('zoom-increase');
|
||||||
|
const zoomPresets = document.querySelectorAll('.zoom-preset-btn');
|
||||||
|
|
||||||
const initScale = Number(localStorage.getItem(DISPLAY_SCALE_KEY) || 100);
|
let currentScale = Number(localStorage.getItem(DISPLAY_SCALE_KEY) || 100);
|
||||||
if (scaleSlider) {
|
|
||||||
scaleSlider.value = String(initScale);
|
// Function to apply zoom
|
||||||
if (scaleValue) scaleValue.textContent = initScale + '%';
|
async function applyZoom(scale) {
|
||||||
|
currentScale = Math.max(50, Math.min(300, scale));
|
||||||
|
if (scaleValue) scaleValue.textContent = currentScale + '%';
|
||||||
|
localStorage.setItem(DISPLAY_SCALE_KEY, String(currentScale));
|
||||||
|
|
||||||
|
// Highlight active preset
|
||||||
|
zoomPresets.forEach(btn => {
|
||||||
|
btn.classList.toggle('active', Number(btn.dataset.zoom) === currentScale);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ipc && typeof ipc.invoke === 'function') {
|
||||||
|
try {
|
||||||
|
const zoomFactor = currentScale / 100;
|
||||||
|
await ipc.invoke('set-zoom-factor', zoomFactor);
|
||||||
|
showStatus(`Zoom set to ${currentScale}%`);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to apply zoom:', err);
|
||||||
|
showStatus(`Zoom saved to ${currentScale}%`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaleSlider) {
|
// Initialize display
|
||||||
scaleSlider.addEventListener('input', () => {
|
if (scaleValue) scaleValue.textContent = currentScale + '%';
|
||||||
const val = Number(scaleSlider.value);
|
zoomPresets.forEach(btn => {
|
||||||
if (scaleValue) scaleValue.textContent = val + '%';
|
btn.classList.toggle('active', Number(btn.dataset.zoom) === currentScale);
|
||||||
localStorage.setItem(DISPLAY_SCALE_KEY, String(val));
|
});
|
||||||
showStatus(`Display scale set to ${val}%`);
|
|
||||||
|
// Apply saved zoom on load
|
||||||
|
if (ipc && typeof ipc.invoke === 'function' && currentScale !== 100) {
|
||||||
|
try {
|
||||||
|
const zoomFactor = currentScale / 100;
|
||||||
|
ipc.invoke('set-zoom-factor', zoomFactor).catch(err => {
|
||||||
|
console.warn('Failed to apply initial zoom:', err);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to apply initial zoom:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease button
|
||||||
|
if (zoomDecrease) {
|
||||||
|
zoomDecrease.addEventListener('click', () => {
|
||||||
|
applyZoom(currentScale - 10);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increase button
|
||||||
|
if (zoomIncrease) {
|
||||||
|
zoomIncrease.addEventListener('click', () => {
|
||||||
|
applyZoom(currentScale + 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preset buttons
|
||||||
|
zoomPresets.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const zoom = Number(btn.dataset.zoom);
|
||||||
|
applyZoom(zoom);
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (e) { console.warn('Display scale setup failed', e); }
|
} catch (e) { console.warn('Display scale setup failed', e); }
|
||||||
|
|
||||||
// Big Picture Mode controls
|
// Big Picture Mode controls
|
||||||
|
|||||||
Reference in New Issue
Block a user