diff --git a/docs/assets/image.png b/docs/assets/image.png index 61bf640..7611621 100644 Binary files a/docs/assets/image.png and b/docs/assets/image.png differ diff --git a/src/main/window.ts b/src/main/window.ts index be72864..52b8ff6 100644 --- a/src/main/window.ts +++ b/src/main/window.ts @@ -69,6 +69,9 @@ let tabs: Tab[] = [] let activeId = '' let tabSeq = 0 let overlayCollapsed = false +// Chrome expandido: cubre toda la ventana (transparente) para que un popup del +// chrome (dropdown de historial) se dibuje sobre la página sin recortarse. +let chromeExpanded = false // Posición del overlay: null = anclado a la esquina (default, responsive); // una vez arrastrado, queda fijo en {x,y} (top-left). let overlayPos: { x: number; y: number } | null = null @@ -103,7 +106,7 @@ function cornerPos(width: number, height: number, o: { w: number; h: number }): function layout(): void { const { width, height } = win.getContentBounds() const ch = chromeHeight() - chromeView.setBounds({ x: 0, y: 0, width, height: ch }) + chromeView.setBounds({ x: 0, y: 0, width, height: chromeExpanded ? height : ch }) const at = activeTab() if (at) at.view.setBounds({ x: 0, y: ch, width, height: height - ch }) @@ -268,6 +271,7 @@ export function createWindow(): void { chromeView = new WebContentsView({ webPreferences: uiPrefs }) overlayView = new WebContentsView({ webPreferences: uiPrefs }) overlayView.setBackgroundColor('#00000000') // transparente donde el panel no pinta + chromeView.setBackgroundColor('#00000000') // transparente bajo el chrome (popups sobre la página) win.contentView.addChildView(chromeView) win.contentView.addChildView(overlayView) @@ -301,6 +305,12 @@ export function createWindow(): void { } function registerIpc(): void { + // Expande/contrae el chrome para popups que deben flotar sobre la página. + ipcMain.on(IPC.chromeExpand, (_e, open: boolean) => { + chromeExpanded = open + layout() + }) + ipcMain.on(IPC.navigate, (_e, url: string) => { activeTab()?.view.webContents.loadURL(normUrl(url)).catch((err) => console.error('[nav]', err)) }) diff --git a/src/preload/index.ts b/src/preload/index.ts index 183b21e..5b571d1 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -84,6 +84,9 @@ const api = { getResponseBody(requestId: string): Promise { return ipcRenderer.invoke(IPC.getResponseBody, requestId) }, + chromeExpand(open: boolean): void { + ipcRenderer.send(IPC.chromeExpand, open) + }, onOverlayState(fn: (collapsed: boolean) => void): () => void { const listener = (_e: unknown, collapsed: boolean): void => fn(collapsed) ipcRenderer.on(IPC.overlayState, listener) diff --git a/src/renderer/chrome/Chrome.tsx b/src/renderer/chrome/Chrome.tsx index 033c338..b15cc80 100644 --- a/src/renderer/chrome/Chrome.tsx +++ b/src/renderer/chrome/Chrome.tsx @@ -7,6 +7,9 @@ export function Chrome(): JSX.Element { const [draft, setDraft] = useState('') const [tabs, setTabs] = useState({ tabs: [], activeId: '' }) const [marks, setMarks] = useState({ items: [], barVisible: false, currentSaved: false }) + // Histórico de las últimas 5 búsquedas/URLs (sesión; el chrome no se recarga). + const [history, setHistory] = useState([]) + const [showHist, setShowHist] = useState(false) useEffect(() => window.overrun.onNavState((s) => { setNav(s) @@ -15,13 +18,28 @@ export function Chrome(): JSX.Element { useEffect(() => window.overrun.onTabsState(setTabs), []) useEffect(() => window.overrun.onBookmarksState(setMarks), []) - const go = (): void => window.overrun.navigate(draft) + const navigateTo = (raw: string): void => { + const url = raw.trim() + if (!url) return + window.overrun.navigate(url) + setHistory((h) => [url, ...h.filter((u) => u !== url)].slice(0, 5)) + setShowHist(false) + } + const go = (): void => navigateTo(draft) + + const suggestions = history.filter((u) => draft.trim() === '' || u.toLowerCase().includes(draft.toLowerCase())) + + // El dropdown se recorta por los bounds del chromeView; cuando está visible se + // expande la vista del chrome a toda la ventana (transparente) para que flote. + useEffect(() => { + window.overrun.chromeExpand(showHist && suggestions.length > 0) + }, [showHist, suggestions.length]) return ( -
+
{/* tab strip — zona de arrastre de la ventana (frameless); el padding derecho reserva el hueco de los controles nativos (overlay). */} -
+
@@ -30,14 +48,14 @@ export function Chrome(): JSX.Element {
{/* pestañas abiertas */} -
+
{tabs.tabs.map((t) => { const active = t.id === tabs.activeId return (
window.overrun.tabActivate(t.id)} title={t.url || t.title} - style={{ display: 'flex', alignItems: 'center', gap: 7, height: 30, padding: '0 8px 0 11px', borderRadius: '8px 8px 0 0', cursor: 'pointer', maxWidth: 200, minWidth: 96, background: active ? 'var(--surface-2)' : 'transparent', border: '1px solid', borderColor: active ? 'var(--line-soft)' : 'transparent', borderBottom: 'none' }}> + style={{ display: 'flex', alignItems: 'center', gap: 7, height: '100%', padding: '0 8px 0 11px', borderRadius: '8px 8px 0 0', cursor: 'pointer', maxWidth: 200, minWidth: 96, background: active ? 'var(--surface-2)' : 'transparent', border: '1px solid', borderColor: active ? 'var(--line-soft)' : 'transparent', borderBottom: 'none' }}> {t.title || 'nueva pestaña'} @@ -51,17 +69,19 @@ export function Chrome(): JSX.Element {
) })} -
+ + {/* nueva pestaña — fuera del scroll de tabs, siempre visible junto a la última */} +
{/* toolbar */} -
+
window.overrun.navAction('back')} disabled={!nav.canGoBack} d="M10 3 L5 8 L10 13" /> window.overrun.navAction('forward')} disabled={!nav.canGoForward} d="M6 3 L11 8 L6 13" /> @@ -69,14 +89,27 @@ export function Chrome(): JSX.Element { window.overrun.bookmarkToggle()} /> window.overrun.bookmarksBarToggle()} />
- setDraft(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && go()} - spellCheck={false} - placeholder="Ingresá una URL" - style={{ flex: 1, height: 34, padding: '0 14px', borderRadius: 9, background: 'var(--surface)', border: '1px solid var(--line)', color: '#cfd3d9', fontFamily: 'var(--font-mono)', fontSize: 12.5, outline: 'none', userSelect: 'text' }} - /> + {/* barra de direcciones + dropdown de historial (custom, en paleta) */} +
+ setDraft(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && go()} + onFocus={() => setShowHist(true)} + onBlur={() => setShowHist(false)} + spellCheck={false} + placeholder="Ingresá una URL" + style={{ width: '100%', height: 34, padding: '0 14px', borderRadius: 9, background: 'var(--surface)', border: '1px solid var(--line)', color: '#cfd3d9', fontFamily: 'var(--font-mono)', fontSize: 12.5, outline: 'none', userSelect: 'text' }} + /> + {showHist && suggestions.length > 0 && ( +
+
RECIENTES
+ {suggestions.map((u) => ( + navigateTo(u)} /> + ))} +
+ )} +
{/* resolución actual del viewport de la página */}
@@ -149,6 +182,24 @@ function BarToggle({ active, onClick }: { active: boolean; onClick: () => void } ) } +// Fila del historial: hover en paleta; onMouseDown navega antes del blur del input. +function HistRow({ url, onPick }: { url: string; onPick: () => void }): JSX.Element { + const [hover, setHover] = useState(false) + return ( +
{ e.preventDefault(); onPick() }} + onMouseEnter={() => setHover(true)} + onMouseLeave={() => setHover(false)} + style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '7px 12px', cursor: 'pointer', background: hover ? 'oklch(0.82 0.15 195 / 0.1)' : 'transparent' }}> + + + + + {url} +
+ ) +} + function hostOf(url: string): string { try { return new URL(url).host diff --git a/src/shared/events.ts b/src/shared/events.ts index 3c4f7d9..5ba97e4 100644 --- a/src/shared/events.ts +++ b/src/shared/events.ts @@ -179,6 +179,9 @@ export const IPC = { overlayResize: 'overrun:overlay-resize', /** overlay → main (invoke): pide el body de una respuesta on-demand. */ getResponseBody: 'overrun:get-response-body', + /** chrome → main: expandir/contraer la vista del chrome para que un popup + * (dropdown de historial) pueda dibujarse sobre la página sin recortarse. */ + chromeExpand: 'overrun:chrome-expand', // ---- pestañas (multi-tab) ---- /** main → chrome: lista de pestañas + cuál está activa. */