webview zoom (#1531)

This commit is contained in:
Mike Sawka 2024-12-16 14:16:21 -08:00 committed by GitHub
parent 2285ac99db
commit 38deb28da5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 9 deletions

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { BlockNodeModel } from "@/app/block/blocktypes";
import { getApi, getSettingsKeyAtom, openLink } from "@/app/store/global";
import { getApi, getBlockMetaKeyAtom, getSettingsKeyAtom, openLink } from "@/app/store/global";
import { getSimpleControlShiftAtom } from "@/app/store/keymodel";
import { ObjectService } from "@/app/store/services";
import { RpcApi } from "@/app/store/wshclientapi";
@ -49,13 +49,13 @@ export class WebViewModel implements ViewModel {
mediaPlaying: PrimitiveAtom<boolean>;
mediaMuted: PrimitiveAtom<boolean>;
modifyExternalUrl?: (url: string) => string;
domReady: PrimitiveAtom<boolean>;
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.nodeModel = nodeModel;
this.viewType = "web";
this.blockId = blockId;
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
this.url = atom();
const defaultUrlAtom = getSettingsKeyAtom("web:defaulturl");
this.homepageUrl = atom((get) => {
@ -71,6 +71,7 @@ export class WebViewModel implements ViewModel {
this.viewName = atom("Web");
this.urlInputRef = createRef<HTMLInputElement>();
this.webviewRef = createRef<WebviewTag>();
this.domReady = atom(false);
this.mediaPlaying = atom(false);
this.mediaMuted = atom(false);
@ -339,7 +340,7 @@ export class WebViewModel implements ViewModel {
const defaultSearchAtom = getSettingsKeyAtom("web:defaultsearch");
const searchTemplate = globalStore.get(defaultSearchAtom);
const nextUrl = this.ensureUrlScheme(newUrl, searchTemplate);
console.log("webview loadUrl", reason, nextUrl, "cur=", this.webviewRef?.current.getURL());
console.log("webview loadUrl", reason, nextUrl, "cur=", this.webviewRef.current.getURL());
if (!this.webviewRef.current) {
return;
}
@ -414,7 +415,7 @@ export class WebViewModel implements ViewModel {
return true;
}
if (checkKeyPressed(e, "Cmd:r")) {
this.webviewRef?.current?.reload();
this.webviewRef.current?.reload();
return true;
}
if (checkKeyPressed(e, "Cmd:ArrowLeft")) {
@ -428,7 +429,61 @@ export class WebViewModel implements ViewModel {
return false;
}
setZoomFactor(factor: number | null) {
// null is ok (will reset to default)
if (factor != null && factor < 0.1) {
factor = 0.1;
}
if (factor != null && factor > 5) {
factor = 5;
}
const domReady = globalStore.get(this.domReady);
if (!domReady) {
return;
}
this.webviewRef.current?.setZoomFactor(factor || 1);
RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "web:zoom": factor }, // allow null so we can remove the zoom factor here
});
}
getSettingsMenuItems(): ContextMenuItem[] {
const zoomSubMenu: ContextMenuItem[] = [];
let curZoom = 1;
if (globalStore.get(this.domReady)) {
curZoom = this.webviewRef.current?.getZoomFactor() || 1;
}
const model = this; // for the closure to work (this is getting unset)
function makeZoomFactorMenuItem(label: string, factor: number): ContextMenuItem {
return {
label: label,
type: "checkbox",
click: () => {
model.setZoomFactor(factor);
},
checked: curZoom == factor,
};
}
zoomSubMenu.push({
label: "Reset",
click: () => {
model.setZoomFactor(null);
},
});
zoomSubMenu.push(makeZoomFactorMenuItem("25%", 0.25));
zoomSubMenu.push(makeZoomFactorMenuItem("50%", 0.5));
zoomSubMenu.push(makeZoomFactorMenuItem("70%", 0.7));
zoomSubMenu.push(makeZoomFactorMenuItem("80%", 0.8));
zoomSubMenu.push(makeZoomFactorMenuItem("90%", 0.9));
zoomSubMenu.push(makeZoomFactorMenuItem("100%", 1));
zoomSubMenu.push(makeZoomFactorMenuItem("110%", 1.1));
zoomSubMenu.push(makeZoomFactorMenuItem("120%", 1.2));
zoomSubMenu.push(makeZoomFactorMenuItem("130%", 1.3));
zoomSubMenu.push(makeZoomFactorMenuItem("150%", 1.5));
zoomSubMenu.push(makeZoomFactorMenuItem("175%", 1.75));
zoomSubMenu.push(makeZoomFactorMenuItem("200%", 2));
return [
{
label: "Set Block Homepage",
@ -441,6 +496,10 @@ export class WebViewModel implements ViewModel {
{
type: "separator",
},
{
label: "Set Zoom Factor",
submenu: zoomSubMenu,
},
{
label: this.webviewRef.current?.isDevToolsOpened() ? "Close DevTools" : "Open DevTools",
click: () => {
@ -476,12 +535,13 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
let metaUrl = blockData?.meta?.url || defaultUrl;
metaUrl = model.ensureUrlScheme(metaUrl, defaultSearch);
const metaUrlRef = useRef(metaUrl);
const zoomFactor = useAtomValue(getBlockMetaKeyAtom(model.blockId, "web:zoom")) || 1;
// The initial value of the block metadata URL when the component first renders. Used to set the starting src value for the webview.
const [metaUrlInitial] = useState(metaUrl);
const [webContentsId, setWebContentsId] = useState(null);
const [domReady, setDomReady] = useState(false);
const domReady = useAtomValue(model.domReady);
const [errorText, setErrorText] = useState("");
@ -510,13 +570,27 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
}
useEffect(() => {
if (model.webviewRef.current && domReady) {
return () => {
globalStore.set(model.domReady, false);
};
}, []);
useEffect(() => {
if (model.webviewRef.current == null || !domReady) {
return;
}
try {
const wcId = model.webviewRef.current.getWebContentsId?.();
if (wcId) {
setWebContentsId(wcId);
if (model.webviewRef.current.getZoomFactor() != zoomFactor) {
model.webviewRef.current.setZoomFactor(zoomFactor);
}
}
} catch (e) {
console.error("Failed to get webcontentsid / setzoomlevel (webview)", e);
}
}, [model.webviewRef.current, domReady]);
}, [model.webviewRef.current, domReady, zoomFactor]);
// Load a new URL if the block metadata is updated.
useEffect(() => {
@ -560,7 +634,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
console.error(errorMessage);
setErrorText(errorMessage);
if (onFailLoad) {
const curUrl = model.webviewRef?.current.getURL();
const curUrl = model.webviewRef.current.getURL();
onFailLoad(curUrl);
}
}
@ -573,7 +647,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
getApi().setWebviewFocus(null);
};
const handleDomReady = () => {
setDomReady(true);
globalStore.set(model.domReady, true);
setBgColor();
};
const handleMediaPlaying = () => {

View File

@ -489,6 +489,7 @@ declare global {
"term:scrollback"?: number;
"term:vdomblockid"?: string;
"term:vdomtoolbarblockid"?: string;
"web:zoom"?: number;
"vdom:*"?: boolean;
"vdom:initialized"?: boolean;
"vdom:correlationid"?: string;

View File

@ -94,6 +94,8 @@ const (
MetaKey_TermVDomSubBlockId = "term:vdomblockid"
MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid"
MetaKey_WebZoom = "web:zoom"
MetaKey_VDomClear = "vdom:*"
MetaKey_VDomInitialized = "vdom:initialized"
MetaKey_VDomCorrelationId = "vdom:correlationid"

View File

@ -95,6 +95,8 @@ type MetaTSType struct {
TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"`
TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"`
WebZoom float64 `json:"web:zoom,omitempty"`
VDomClear bool `json:"vdom:*,omitempty"`
VDomInitialized bool `json:"vdom:initialized,omitempty"`
VDomCorrelationId string `json:"vdom:correlationid,omitempty"`