Theseus: a page click closes the left panel unless it is pinned
Opera's other sidebar rule. A click in the active tab's page now hides the left panel (web app or add-on panel), so a chat opened for a quick look gets out of the way on its own. A pin button in the strip, shown while a panel is open, keeps that app open instead; pins are per app and survive restarts (settings.leftPanelPinned). The click is reported by home-preload, which every web tab already runs; main ignores it unless a panel is open and the click came from the active tab.
This commit is contained in:
parent
29b346250f
commit
7907451b24
4 changed files with 44 additions and 0 deletions
|
|
@ -5,6 +5,9 @@
|
||||||
// origin-mismatched call, so a third-party page can inspect the API's
|
// origin-mismatched call, so a third-party page can inspect the API's
|
||||||
// SHAPE but can't invoke it against local user data.
|
// SHAPE but can't invoke it against local user data.
|
||||||
const { contextBridge, ipcRenderer } = require("electron");
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
|
// A click in the page closes the left panel (web app / add-on) unless it is
|
||||||
|
// pinned. Main ignores this unless a panel is open and the tab is active.
|
||||||
|
window.addEventListener("pointerdown", () => { ipcRenderer.send("tab-pointerdown"); }, true);
|
||||||
contextBridge.exposeInMainWorld("home", {
|
contextBridge.exposeInMainWorld("home", {
|
||||||
getCards: () => ipcRenderer.invoke("home-cards-get"),
|
getCards: () => ipcRenderer.invoke("home-cards-get"),
|
||||||
setCards: (cards) => ipcRenderer.invoke("home-cards-set", cards),
|
setCards: (cards) => ipcRenderer.invoke("home-cards-set", cards),
|
||||||
|
|
|
||||||
25
main.js
25
main.js
|
|
@ -7951,6 +7951,7 @@ function quickLinksPayload() {
|
||||||
return {
|
return {
|
||||||
links: settings.quickLinks || [],
|
links: settings.quickLinks || [],
|
||||||
openId: leftPanelId ? "p:" + leftPanelId : activeQuickLinkId,
|
openId: leftPanelId ? "p:" + leftPanelId : activeQuickLinkId,
|
||||||
|
pinned: leftPanelPinned(),
|
||||||
panels: leftPanels().map((p) => ({ id: "p:" + p.panelId, title: p.addonName || p.title, icon: p.icon || "" })),
|
panels: leftPanels().map((p) => ({ id: "p:" + p.panelId, title: p.addonName || p.title, icon: p.icon || "" })),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -7977,6 +7978,30 @@ function raisePanelGrip() {
|
||||||
// Opera-style: the left panel steps aside when the user opens something else
|
// Opera-style: the left panel steps aside when the user opens something else
|
||||||
// (a new tab, the right sidebar). It is only hidden — the app keeps its
|
// (a new tab, the right sidebar). It is only hidden — the app keeps its
|
||||||
// state, so reopening it is instant and lands where the user left off.
|
// state, so reopening it is instant and lands where the user left off.
|
||||||
|
// Opera's other rule: a click in the page closes the panel, unless the user
|
||||||
|
// pinned that app (settings.leftPanelPinned, keyed like leftPanelWidths).
|
||||||
|
function leftPanelPinned() {
|
||||||
|
const k = leftPanelKey();
|
||||||
|
return !!(k && settings.leftPanelPinned && settings.leftPanelPinned[k]);
|
||||||
|
}
|
||||||
|
ipcMain.handle("quicklinks-pin", (e, on) => {
|
||||||
|
if (!quicklinks || e.sender !== quicklinks.webContents) return false;
|
||||||
|
const k = leftPanelKey();
|
||||||
|
if (!k) return false;
|
||||||
|
const next = { ...(settings.leftPanelPinned || {}) };
|
||||||
|
if (on) next[k] = true; else delete next[k];
|
||||||
|
settings.leftPanelPinned = next;
|
||||||
|
saveSettings();
|
||||||
|
emitQuickLinksState();
|
||||||
|
return !!on;
|
||||||
|
});
|
||||||
|
// home-preload reports a pointerdown in a web tab's page.
|
||||||
|
ipcMain.on("tab-pointerdown", (e) => {
|
||||||
|
if (!leftPanelKey() || leftPanelPinned()) return;
|
||||||
|
const t = tabs.find((x) => x.view?.webContents === e.sender);
|
||||||
|
if (!t || t.id !== activeId) return;
|
||||||
|
hideLeftPanels();
|
||||||
|
});
|
||||||
function hideLeftPanels() {
|
function hideLeftPanels() {
|
||||||
if (activeQuickLinkId) closeQuickPanel();
|
if (activeQuickLinkId) closeQuickPanel();
|
||||||
if (leftPanelId) closeLeftPanel();
|
if (leftPanelId) closeLeftPanel();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ contextBridge.exposeInMainWorld("quicklinks", {
|
||||||
getState: () => ipcRenderer.invoke("quicklinks-get-state"),
|
getState: () => ipcRenderer.invoke("quicklinks-get-state"),
|
||||||
open: (id) => ipcRenderer.invoke("quicklinks-open", String(id || "")),
|
open: (id) => ipcRenderer.invoke("quicklinks-open", String(id || "")),
|
||||||
close: () => ipcRenderer.invoke("quicklinks-close"),
|
close: () => ipcRenderer.invoke("quicklinks-close"),
|
||||||
|
pin: (on) => ipcRenderer.invoke("quicklinks-pin", !!on),
|
||||||
add: () => ipcRenderer.invoke("quicklinks-add"),
|
add: () => ipcRenderer.invoke("quicklinks-add"),
|
||||||
onState: (cb) => ipcRenderer.on("quicklinks-state", (_e, d) => cb(d)),
|
onState: (cb) => ipcRenderer.on("quicklinks-state", (_e, d) => cb(d)),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,19 @@
|
||||||
.sep { width: 22px; height: 1px; background: var(--line2); margin: 4px 0; }
|
.sep { width: 22px; height: 1px; background: var(--line2); margin: 4px 0; }
|
||||||
.icon.emoji { font-size: 18px; }
|
.icon.emoji { font-size: 18px; }
|
||||||
.add:hover { color: var(--acid); }
|
.add:hover { color: var(--acid); }
|
||||||
|
/* Pin: keeps the open panel when the page is clicked. Shown only while a
|
||||||
|
panel is open; lit when that app is pinned. */
|
||||||
|
.pin { color: var(--mut); margin-bottom: 4px; }
|
||||||
|
.pin[hidden] { display: none; }
|
||||||
|
.pin.on { color: var(--acid); background: var(--acid-tint); }
|
||||||
|
.pin svg { width: 16px; height: 16px; }
|
||||||
</style></head>
|
</style></head>
|
||||||
<body>
|
<body>
|
||||||
<div class="strip">
|
<div class="strip">
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="icons" id="icons"></div>
|
<div class="icons" id="icons"></div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
|
<button class="icon pin" id="pinBtn" hidden><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 4h6l-1 6 3 3v2H7v-2l3-3-1-6z"/><path d="M12 15v6"/></svg><span class="tt" id="pinTt">Keep open</span></button>
|
||||||
<button class="icon add" id="addBtn" title="Add link">+<span class="tt">Add link</span></button>
|
<button class="icon add" id="addBtn" title="Add link">+<span class="tt">Add link</span></button>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -52,6 +59,10 @@
|
||||||
const icons = document.getElementById("icons");
|
const icons = document.getElementById("icons");
|
||||||
const addBtn = document.getElementById("addBtn");
|
const addBtn = document.getElementById("addBtn");
|
||||||
addBtn.onclick = () => Q && Q.add();
|
addBtn.onclick = () => Q && Q.add();
|
||||||
|
const pinBtn = document.getElementById("pinBtn");
|
||||||
|
const pinTt = document.getElementById("pinTt");
|
||||||
|
let pinned = false;
|
||||||
|
pinBtn.onclick = () => Q && Q.pin && Q.pin(!pinned);
|
||||||
// Bundled brand marks. Each SVG is tight (20×20 viewBox, no padding), the
|
// Bundled brand marks. Each SVG is tight (20×20 viewBox, no padding), the
|
||||||
// colour is baked in so icons read at a glance in both themes. Unknown ids
|
// colour is baked in so icons read at a glance in both themes. Unknown ids
|
||||||
// fall back to a letter chip. All marks are purely to identify services.
|
// fall back to a letter chip. All marks are purely to identify services.
|
||||||
|
|
@ -69,6 +80,10 @@
|
||||||
function render(data) {
|
function render(data) {
|
||||||
const links = (data && data.links) || [];
|
const links = (data && data.links) || [];
|
||||||
const openId = (data && data.openId) || null;
|
const openId = (data && data.openId) || null;
|
||||||
|
pinned = !!(data && data.pinned);
|
||||||
|
pinBtn.hidden = !openId;
|
||||||
|
pinBtn.classList.toggle("on", pinned);
|
||||||
|
pinTt.textContent = pinned ? "Pinned: stays open when you click the page" : "Keep open when you click the page";
|
||||||
icons.innerHTML = "";
|
icons.innerHTML = "";
|
||||||
// Extension panels (registered with side: "left") come first.
|
// Extension panels (registered with side: "left") come first.
|
||||||
const panels = (data && data.panels) || [];
|
const panels = (data && data.panels) || [];
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue