From 20af91623fb6c0a63f2963c54d5d5f71874555a4 Mon Sep 17 00:00:00 2001 From: Justin Bennett Date: Thu, 26 Sep 2024 14:45:05 -0400 Subject: [PATCH] Support window sizing (#61) * `accept_first_mouse` -> `acceptFirstMouse` * Add support for configuring webview size * Prepare 0.0.12 (binary 0.1.10) * Add ability to set window size after init * Return scale factor with sizing * Update changelog --- CHANGELOG.md | 25 ++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- deno.json | 2 +- examples/window-size.ts | 36 +++++++++ schemas/WebViewMessage.json | 47 ++++++++++- schemas/WebViewOptions.json | 51 ++++++++++-- schemas/WebViewRequest.json | 140 +++++++++++++++++++++++++++++++- schemas/WebViewResponse.json | 47 ++++++++++- scripts/generate-schema.ts | 101 ++++++++++++++++++++---- src/lib.ts | 119 +++++++++++++++++++--------- src/main.rs | 149 ++++++++++++++++++++++++++++++++--- src/schemas.ts | 123 +++++++++++++++++++++++++---- 13 files changed, 746 insertions(+), 98 deletions(-) create mode 100644 examples/window-size.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a83acd9..9b6c2b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.0.12 (binary 0.1.10) -- 2024-09-26 + +BREAKING CHANGES + +- `WebViewOptions` `accept_first_mouse` is now `acceptFirstMouse` +- `WebViewOptions` `fullscreen` was removed in favor of `size` + +Additions + +- The webview size can be altered by providing `WebViewOptions` `size` as either `"maximized"`, `"fullscreen"`, or `{ width: number, height: number }` +- added `webview.maximize()` +- added `webview.minimize()` +- added `webview.fullscreen()` +- added `webview.getSize()` +- added `webview.setSize({ ... })` + +Fixes + +- `webview.on` and `webivew.once` had their types improved to actually return the result of their event payload + +Misc + +- Tao updated to v0.30.2 +- Wry upgraded to v0.45.0 + ## 0.0.11 (binary 0.1.9) -- 2024-09-23 - Adds more doc comments diff --git a/Cargo.lock b/Cargo.lock index 178460e..c70b571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,7 +278,7 @@ dependencies = [ [[package]] name = "deno-webview" -version = "0.1.9" +version = "0.1.10" dependencies = [ "schemars", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1379b1b..fa5bd04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deno-webview" -version = "0.1.9" +version = "0.1.10" edition = "2021" [profile.release] diff --git a/deno.json b/deno.json index 8f01a1e..5aa6d13 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,7 @@ { "name": "@justbe/webview", "exports": "./src/lib.ts", - "version": "0.0.11", + "version": "0.0.12", "tasks": { "dev": "deno run --watch main.ts", "gen": "deno task gen:rust && deno task gen:deno", diff --git a/examples/window-size.ts b/examples/window-size.ts new file mode 100644 index 0000000..db11923 --- /dev/null +++ b/examples/window-size.ts @@ -0,0 +1,36 @@ +import { createWebView } from "../src/lib.ts"; + +using webview = await createWebView({ + title: "Window Size", + html: ` +

Window Sizes

+
+ + + +
+ `, + size: { + height: 200, + width: 800, + }, + ipc: true, +}); + +webview.on("ipc", ({ message }) => { + switch (message) { + case "maximize": + webview.maximize(); + break; + case "minimize": + webview.minimize(); + break; + case "fullscreen": + webview.fullscreen(); + break; + default: + console.error("Unknown message", message); + } +}); + +await webview.waitUntilClosed(); diff --git a/schemas/WebViewMessage.json b/schemas/WebViewMessage.json index 79beec4..3e07170 100644 --- a/schemas/WebViewMessage.json +++ b/schemas/WebViewMessage.json @@ -194,11 +194,11 @@ "$type": { "type": "string", "enum": [ - "json" + "boolean" ] }, "value": { - "type": "string" + "type": "boolean" } } }, @@ -212,11 +212,50 @@ "$type": { "type": "string", "enum": [ - "boolean" + "float" ] }, "value": { - "type": "boolean" + "type": "number", + "format": "double" + } + } + }, + { + "type": "object", + "required": [ + "$type", + "value" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "size" + ] + }, + "value": { + "type": "object", + "required": [ + "height", + "scale_factor", + "width" + ], + "properties": { + "height": { + "type": "number", + "format": "double" + }, + "scale_factor": { + "description": "The ratio between physical and logical sizes.", + "type": "number", + "format": "double" + }, + "width": { + "type": "number", + "format": "double" + } + } } } } diff --git a/schemas/WebViewOptions.json b/schemas/WebViewOptions.json index 36f16bf..d74f39e 100644 --- a/schemas/WebViewOptions.json +++ b/schemas/WebViewOptions.json @@ -35,7 +35,7 @@ "title" ], "properties": { - "accept_first_mouse": { + "acceptFirstMouse": { "description": "Sets whether clicking an inactive window also clicks through to the webview. Default is false.", "default": false, "type": "boolean" @@ -65,11 +65,6 @@ "default": false, "type": "boolean" }, - "fullscreen": { - "description": "When true, the window will be fullscreen. Default is false.", - "default": false, - "type": "boolean" - }, "incognito": { "description": "Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is enabled.\n\nPlatform-specific: - Windows: Requires WebView2 Runtime version 101.0.1210.39 or higher, does nothing on older versions, see https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/archive?tabs=dotnetcsharp#10121039", "default": false, @@ -80,6 +75,17 @@ "default": false, "type": "boolean" }, + "size": { + "description": "The size of the window.", + "anyOf": [ + { + "$ref": "#/definitions/WindowSize" + }, + { + "type": "null" + } + ] + }, "title": { "description": "Sets the title of the window.", "type": "string" @@ -89,5 +95,38 @@ "default": false, "type": "boolean" } + }, + "definitions": { + "WindowSize": { + "anyOf": [ + { + "$ref": "#/definitions/WindowSizeStates" + }, + { + "type": "object", + "required": [ + "height", + "width" + ], + "properties": { + "height": { + "type": "number", + "format": "double" + }, + "width": { + "type": "number", + "format": "double" + } + } + } + ] + }, + "WindowSizeStates": { + "type": "string", + "enum": [ + "maximized", + "fullscreen" + ] + } } } \ No newline at end of file diff --git a/schemas/WebViewRequest.json b/schemas/WebViewRequest.json index db23e9b..340dd61 100644 --- a/schemas/WebViewRequest.json +++ b/schemas/WebViewRequest.json @@ -140,6 +140,144 @@ "type": "string" } } + }, + { + "type": "object", + "required": [ + "$type", + "id" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "getSize" + ] + }, + "id": { + "type": "string" + }, + "include_decorations": { + "default": null, + "type": [ + "boolean", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "$type", + "id", + "size" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "setSize" + ] + }, + "id": { + "type": "string" + }, + "size": { + "$ref": "#/definitions/SimpleSize" + } + } + }, + { + "type": "object", + "required": [ + "$type", + "id" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "fullscreen" + ] + }, + "fullscreen": { + "type": [ + "boolean", + "null" + ] + }, + "id": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "$type", + "id" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "maximize" + ] + }, + "id": { + "type": "string" + }, + "maximized": { + "type": [ + "boolean", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "$type", + "id" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "minimize" + ] + }, + "id": { + "type": "string" + }, + "minimized": { + "type": [ + "boolean", + "null" + ] + } + } + } + ], + "definitions": { + "SimpleSize": { + "type": "object", + "required": [ + "height", + "width" + ], + "properties": { + "height": { + "type": "number", + "format": "double" + }, + "width": { + "type": "number", + "format": "double" + } + } } - ] + } } \ No newline at end of file diff --git a/schemas/WebViewResponse.json b/schemas/WebViewResponse.json index 5448e20..4376963 100644 --- a/schemas/WebViewResponse.json +++ b/schemas/WebViewResponse.json @@ -98,11 +98,11 @@ "$type": { "type": "string", "enum": [ - "json" + "boolean" ] }, "value": { - "type": "string" + "type": "boolean" } } }, @@ -116,11 +116,50 @@ "$type": { "type": "string", "enum": [ - "boolean" + "float" ] }, "value": { - "type": "boolean" + "type": "number", + "format": "double" + } + } + }, + { + "type": "object", + "required": [ + "$type", + "value" + ], + "properties": { + "$type": { + "type": "string", + "enum": [ + "size" + ] + }, + "value": { + "type": "object", + "required": [ + "height", + "scale_factor", + "width" + ], + "properties": { + "height": { + "type": "number", + "format": "double" + }, + "scale_factor": { + "description": "The ratio between physical and logical sizes.", + "type": "number", + "format": "double" + }, + "width": { + "type": "number", + "format": "double" + } + } } } } diff --git a/scripts/generate-schema.ts b/scripts/generate-schema.ts index ca1476b..c206fdc 100644 --- a/scripts/generate-schema.ts +++ b/scripts/generate-schema.ts @@ -4,6 +4,7 @@ import { match, P } from "npm:ts-pattern"; import type { JSONSchema7 as JSONSchema, JSONSchema7Definition as JSONSchemaDefinition, + JSONSchema7TypeName, } from "npm:@types/json-schema"; const schemasDir = new URL("../schemas", import.meta.url).pathname; @@ -36,6 +37,8 @@ type NodeIR = | { type: "boolean"; optional?: boolean } | { type: "string"; optional?: boolean } | { type: "literal"; value: string } + | { type: "int"; minimum?: number; maximum?: number } + | { type: "float"; minimum?: number; maximum?: number } | { type: "unknown" }; const isDescriminatedUnion = (def: JSONSchemaDefinition[] | undefined) => { @@ -43,20 +46,58 @@ const isDescriminatedUnion = (def: JSONSchemaDefinition[] | undefined) => { (def[0]?.required?.[0] + "").startsWith("$"); }; +const isOptionalType = + (typeOf: string) => + (type: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined) => { + if (type && Array.isArray(type) && type[0] === typeOf) { + return true; + } + return false; + }; + function jsonSchemaToIR(schema: JSONSchema): DocIR { const nodeToIR = (node: JSONSchema): NodeIR => { return match(node) .with( - { type: "boolean" }, + { + type: P.union("boolean", P.when(isOptionalType("boolean"))), + }, (node) => - ({ type: "boolean" as const, optional: !!node.default }) as const, + ({ + type: "boolean" as const, + optional: !!node.default, + }) as const, ) + .with({ type: "integer" }, (node) => ({ + type: "int" as const, + minimum: node.minimum, + maximum: node.maximum, + })) + .with({ type: "number", format: "double" }, (node) => ({ + type: "float" as const, + minimum: node.minimum, + maximum: node.maximum, + })) .with( { type: "string" }, - (node) => - node.enum - ? ({ type: "literal" as const, value: node.enum[0] as string }) - : ({ type: "string" as const, optional: !!node.default }), + (node) => { + if (node.enum) { + if (node.enum.length === 1) { + return { + type: "literal" as const, + value: node.enum[0] as string, + }; + } + return { + type: "union" as const, + members: node.enum.map((v) => ({ + type: "literal" as const, + value: v as string, + })), + }; + } + return ({ type: "string" as const, optional: !!node.default }); + }, ) .with( { oneOf: P.when(isDescriminatedUnion) }, @@ -76,7 +117,7 @@ function jsonSchemaToIR(schema: JSONSchema): DocIR { .with( { oneOf: P.array() }, (node) => { - const intersection = { + const union = { type: "union" as const, members: node.oneOf?.map((v) => nodeToIR(v as JSONSchema)) ?? [], }; @@ -95,18 +136,26 @@ function jsonSchemaToIR(schema: JSONSchema): DocIR { value: nodeToIR(value as JSONSchema), })), }, - intersection, + union, ], }); } - return intersection; + return union; }, ) .with( { anyOf: P.array() }, () => ({ type: "union" as const, - members: (node.anyOf?.map((v) => nodeToIR(v as JSONSchema)) ?? []), + members: (node.anyOf?.map((v) => nodeToIR(v as JSONSchema)) ?? []) + .filter((v) => v.type !== "unknown") + // flatten nested unions + .reduce((union, member) => { + if (member.type === "union") { + return union.concat(member.members); + } + return union.concat(member); + }, [] as NodeIR[]), }), ) .with( @@ -115,11 +164,13 @@ function jsonSchemaToIR(schema: JSONSchema): DocIR { type: "object" as const, properties: Object.entries(node.properties ?? {}).map(( [key, value], - ) => ({ - key, - required: node.required?.includes(key) ?? false, - value: nodeToIR(value as JSONSchema), - })), + ) => { + return ({ + key, + required: node.required?.includes(key) ?? false, + value: nodeToIR(value as JSONSchema), + }); + }), }), ) .otherwise(() => ({ type: "unknown" })); @@ -147,6 +198,8 @@ function generateTypes(ir: DocIR) { function generateNode(node: NodeIR) { match(node) + .with({ type: "int" }, () => w("number")) + .with({ type: "float" }, () => w("number")) .with({ type: "boolean" }, () => w("boolean")) .with({ type: "string" }, () => w("string")) .with({ type: "literal" }, (node) => w(`"${node.value}"`)) @@ -205,6 +258,24 @@ function generateZodSchema(ir: DocIR) { function generateNode(node: NodeIR) { match(node) + .with({ type: "int" }, (node) => { + w("z.number().int()"); + if (typeof node.minimum === "number") { + w(`.min(${node.minimum})`); + } + if (typeof node.maximum === "number") { + w(`.max(${node.maximum})`); + } + }) + .with({ type: "float" }, (node) => { + w("z.number()"); + if (typeof node.minimum === "number") { + w(`.min(${node.minimum})`); + } + if (typeof node.maximum === "number") { + w(`.max(${node.maximum})`); + } + }) .with( { type: "boolean" }, (node) => w("z.boolean()", node.optional && ".optional()"), diff --git a/src/lib.ts b/src/lib.ts index 3aabdae..140d18b 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -38,7 +38,7 @@ export type { WebViewOptions } from "./schemas.ts"; // Should match the cargo package version /** The version of the webview binary that's expected */ -export const BIN_VERSION = "0.1.9"; +export const BIN_VERSION = "0.1.10"; type JSON = | string @@ -53,54 +53,36 @@ type WebViewNotification = Extract< { $type: "notification" } >["data"]; -type ResultType = Extract["result"]; -type ResultKinds = Pick["$type"]; +type ResultType = Extract; /** * A helper function for extracting the result from a webview response. + * Throws if the response includes unexpected results. * * @param result - The result of the webview request. * @param expectedType - The format of the expected result. */ -function returnResult( - result: WebViewResponse, - expectedType: "boolean", -): boolean; -function returnResult( - result: WebViewResponse, - expectedType: "string", -): string; -function returnResult( - result: WebViewResponse, - expectedType: "json", -): JSON; -function returnResult( - result: WebViewResponse, - expectedType?: ResultKinds, -): string | JSON | boolean { - switch (result.$type) { - case "result": { - if (expectedType && result.result.$type !== expectedType) { - throw new Error(`unexpected result type: ${result.result.$type}`); - } - const res = result.result; - switch (res.$type) { - case "string": - return res.value; - case "json": - return JSON.parse(res.value) as JSON; - case "boolean": - return res.value; - } - break; +function returnResult< + Response extends WebViewResponse, + E extends ResultType["result"]["$type"], +>( + result: Response, + expectedType: E, +): Extract["value"] { + if (result.$type === "result") { + if (result.result.$type === expectedType) { + // @ts-expect-error TS doesn't correctly narrow this type, but it's correct + return result.result.value; } - case "err": - throw new Error(result.message); - default: - throw new Error(`unexpected response: ${result.$type}`); + throw new Error(`unexpected result type: ${result.result.$type}`); } + throw new Error(`unexpected response: ${result.$type}`); } +/** + * A helper function for acknowledging a webview response. + * Throws if the response includes unexpected results. + */ const returnAck = (result: WebViewResponse) => { switch (result.$type) { case "ack": @@ -369,6 +351,67 @@ export class WebView implements Disposable { return returnResult(result, "string"); } + /** + * Sets the size of the webview window. + * + * Note: this is the logical size of the window, not the physical size. + * @see https://docs.rs/dpi/0.1.1/x86_64-unknown-linux-gnu/dpi/index.html#position-and-size-types + */ + async setSize(size: { width: number; height: number }): Promise { + const result = await this.#send({ $type: "setSize", size }); + return returnAck(result); + } + + /** + * Gets the size of the webview window. + * + * Note: this is the logical size of the window, not the physical size. + * @see https://docs.rs/dpi/0.1.1/x86_64-unknown-linux-gnu/dpi/index.html#position-and-size-types + */ + async getSize( + includeDecorations?: boolean, + ): Promise<{ width: number; height: number; scaleFactor: number }> { + const result = await this.#send({ + $type: "getSize", + include_decorations: includeDecorations, + }); + const { width, height, scale_factor: scaleFactor } = returnResult( + result, + "size", + ); + return { width, height, scaleFactor }; + } + + /** + * Enters or exits fullscreen mode for the webview. + * + * @param fullscreen - If true, the webview will enter fullscreen mode. If false, the webview will exit fullscreen mode. If not specified, the webview will toggle fullscreen mode. + */ + async fullscreen(fullscreen?: boolean): Promise { + const result = await this.#send({ $type: "fullscreen", fullscreen }); + return returnAck(result); + } + + /** + * Maximizes or unmaximizes the webview window. + * + * @param maximized - If true, the webview will be maximized. If false, the webview will be unmaximized. If not specified, the webview will toggle maximized state. + */ + async maximize(maximized?: boolean): Promise { + const result = await this.#send({ $type: "maximize", maximized }); + return returnAck(result); + } + + /** + * Minimizes or unminimizes the webview window. + * + * @param minimized - If true, the webview will be minimized. If false, the webview will be unminimized. If not specified, the webview will toggle minimized state. + */ + async minimize(minimized?: boolean): Promise { + const result = await this.#send({ $type: "minimize", minimized }); + return returnAck(result); + } + /** * Sets the title of the webview window. */ diff --git a/src/main.rs b/src/main.rs index 1933d60..6f89f65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,21 +5,45 @@ use std::sync::mpsc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json; +use tao::dpi::{LogicalSize, Size}; use tao::window::Fullscreen; /// The version of the webview binary. const VERSION: &str = env!("CARGO_PKG_VERSION"); +#[derive(JsonSchema, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct SimpleSize { + width: f64, + height: f64, +} + +#[derive(JsonSchema, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +enum WindowSizeStates { + Maximized, + Fullscreen, +} + +#[derive(JsonSchema, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +enum WindowSize { + States(WindowSizeStates), + Size { width: f64, height: f64 }, +} + /// Options for creating a webview. #[derive(JsonSchema, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] struct WebViewOptions { /// Sets the title of the window. title: String, #[serde(flatten)] target: WebViewTarget, - /// When true, the window will be fullscreen. Default is false. + /// The size of the window. #[serde(default)] - fullscreen: bool, + size: Option, /// When true, the window will have a border, a title bar, etc. Default is true. #[serde(default = "default_true")] decorations: bool, @@ -95,13 +119,51 @@ enum Notification { #[serde(rename_all = "camelCase")] #[serde(tag = "$type")] enum Request { - GetVersion { id: String }, - Eval { id: String, js: String }, - SetTitle { id: String, title: String }, - GetTitle { id: String }, - SetVisibility { id: String, visible: bool }, - IsVisible { id: String }, - OpenDevTools { id: String }, + GetVersion { + id: String, + }, + Eval { + id: String, + js: String, + }, + SetTitle { + id: String, + title: String, + }, + GetTitle { + id: String, + }, + SetVisibility { + id: String, + visible: bool, + }, + IsVisible { + id: String, + }, + OpenDevTools { + id: String, + }, + GetSize { + id: String, + #[serde(default)] + include_decorations: Option, + }, + SetSize { + id: String, + size: SimpleSize, + }, + Fullscreen { + id: String, + fullscreen: Option, + }, + Maximize { + id: String, + maximized: Option, + }, + Minimize { + id: String, + minimized: Option, + }, } /// Responses from the webview to the client. @@ -121,8 +183,14 @@ enum Response { #[allow(dead_code)] enum ResultType { String(String), - Json(String), Boolean(bool), + Float(f64), + Size { + width: f64, + height: f64, + /// The ratio between physical and logical sizes. + scale_factor: f64, + }, } impl From for ResultType { @@ -156,8 +224,18 @@ fn main() -> wry::Result<()> { .with_title(webview_options.title) .with_transparent(webview_options.transparent) .with_decorations(webview_options.decorations); - if webview_options.fullscreen { - window_builder = window_builder.with_fullscreen(Some(Fullscreen::Borderless(None))); + match webview_options.size { + Some(WindowSize::States(WindowSizeStates::Maximized)) => { + window_builder = window_builder.with_maximized(true) + } + Some(WindowSize::States(WindowSizeStates::Fullscreen)) => { + window_builder = window_builder.with_fullscreen(Some(Fullscreen::Borderless(None))) + } + Some(WindowSize::Size { width, height }) => { + window_builder = + window_builder.with_inner_size(Size::Logical(LogicalSize::new(width, height))) + } + None => (), } let window = window_builder.build(&event_loop).unwrap(); @@ -301,6 +379,53 @@ fn main() -> wry::Result<()> { result: VERSION.to_string().into(), }); } + Request::GetSize { + id, + include_decorations, + } => { + let size = if include_decorations.unwrap_or(false) { + window.outer_size().to_logical(window.scale_factor()) + } else { + window.inner_size().to_logical(window.scale_factor()) + }; + res(Response::Result { + id, + result: ResultType::Size { + width: size.width, + height: size.height, + scale_factor: window.scale_factor(), + }, + }); + } + Request::SetSize { id, size } => { + window.set_inner_size(Size::Logical(LogicalSize::new( + size.width, + size.height, + ))); + res(Response::Ack { id }); + } + Request::Fullscreen { id, fullscreen } => { + let fullscreen = fullscreen.unwrap_or(!window.fullscreen().is_some()); + eprintln!("Fullscreen: {:?}", fullscreen); + if fullscreen { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); + } else { + window.set_fullscreen(None); + } + res(Response::Ack { id }); + } + Request::Maximize { id, maximized } => { + let maximized = maximized.unwrap_or(!window.is_maximized()); + eprintln!("Maximize: {:?}", maximized); + window.set_maximized(maximized); + res(Response::Ack { id }); + } + Request::Minimize { id, minimized } => { + let minimized = minimized.unwrap_or(!window.is_minimized()); + eprintln!("Minimize: {:?}", minimized); + window.set_minimized(minimized); + res(Response::Ack { id }); + } } } } diff --git a/src/schemas.ts b/src/schemas.ts index cca2709..b06df53 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -7,7 +7,7 @@ import { z } from "npm:zod"; export type WebViewOptions = & { /** Sets whether clicking an inactive window also clicks through to the webview. Default is false. */ - accept_first_mouse?: boolean; + acceptFirstMouse?: boolean; /** When true, all media can be played without user interaction. Default is false. */ autoplay?: boolean; /** @@ -26,8 +26,6 @@ export type WebViewOptions = devtools?: boolean; /** Sets whether the webview should be focused when created. Default is false. */ focused?: boolean; - /** When true, the window will be fullscreen. Default is false. */ - fullscreen?: boolean; /** * Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is enabled. * @@ -36,6 +34,11 @@ export type WebViewOptions = incognito?: boolean; /** Sets whether host should be able to receive messages from the webview via `window.ipc.postMessage`. */ ipc?: boolean; + /** The size of the window. */ + size?: "maximized" | "fullscreen" | { + height: number; + width: number; + }; /** Sets the title of the window. */ title: string; /** Sets whether the window should be transparent. */ @@ -51,15 +54,20 @@ export type WebViewOptions = ); export const WebViewOptions: z.ZodType = z.intersection( z.object({ - accept_first_mouse: z.boolean().optional(), + acceptFirstMouse: z.boolean().optional(), autoplay: z.boolean().optional(), clipboard: z.boolean().optional(), decorations: z.boolean().optional(), devtools: z.boolean().optional(), focused: z.boolean().optional(), - fullscreen: z.boolean().optional(), incognito: z.boolean().optional(), ipc: z.boolean().optional(), + size: z.union([ + z.literal("maximized"), + z.literal("fullscreen"), + z.object({ height: z.number(), width: z.number() }), + ]) + .optional(), title: z.string(), transparent: z.boolean().optional(), }), @@ -100,6 +108,34 @@ export type WebViewRequest = | { $type: "openDevTools"; id: string; + } + | { + $type: "getSize"; + id: string; + include_decorations?: boolean; + } + | { + $type: "setSize"; + id: string; + size: { + height: number; + width: number; + }; + } + | { + $type: "fullscreen"; + fullscreen?: boolean; + id: string; + } + | { + $type: "maximize"; + id: string; + maximized?: boolean; + } + | { + $type: "minimize"; + id: string; + minimized?: boolean; }; export const WebViewRequest: z.ZodType = z.discriminatedUnion( "$type", @@ -119,6 +155,31 @@ export const WebViewRequest: z.ZodType = z.discriminatedUnion( }), z.object({ $type: z.literal("isVisible"), id: z.string() }), z.object({ $type: z.literal("openDevTools"), id: z.string() }), + z.object({ + $type: z.literal("getSize"), + id: z.string(), + include_decorations: z.boolean().optional(), + }), + z.object({ + $type: z.literal("setSize"), + id: z.string(), + size: z.object({ height: z.number(), width: z.number() }), + }), + z.object({ + $type: z.literal("fullscreen"), + fullscreen: z.boolean().optional(), + id: z.string(), + }), + z.object({ + $type: z.literal("maximize"), + id: z.string(), + maximized: z.boolean().optional(), + }), + z.object({ + $type: z.literal("minimize"), + id: z.string(), + minimized: z.boolean().optional(), + }), ], ); @@ -138,13 +199,21 @@ export type WebViewResponse = $type: "string"; value: string; } - | { - $type: "json"; - value: string; - } | { $type: "boolean"; value: boolean; + } + | { + $type: "float"; + value: number; + } + | { + $type: "size"; + value: { + height: number; + scale_factor: number; + width: number; + }; }; } | { @@ -161,8 +230,16 @@ export const WebViewResponse: z.ZodType = z.discriminatedUnion( id: z.string(), result: z.discriminatedUnion("$type", [ z.object({ $type: z.literal("string"), value: z.string() }), - z.object({ $type: z.literal("json"), value: z.string() }), z.object({ $type: z.literal("boolean"), value: z.boolean() }), + z.object({ $type: z.literal("float"), value: z.number() }), + z.object({ + $type: z.literal("size"), + value: z.object({ + height: z.number(), + scale_factor: z.number(), + width: z.number(), + }), + }), ]), }), z.object({ $type: z.literal("err"), id: z.string(), message: z.string() }), @@ -203,13 +280,21 @@ export type WebViewMessage = $type: "string"; value: string; } - | { - $type: "json"; - value: string; - } | { $type: "boolean"; value: boolean; + } + | { + $type: "float"; + value: number; + } + | { + $type: "size"; + value: { + height: number; + scale_factor: number; + width: number; + }; }; } | { @@ -238,8 +323,16 @@ export const WebViewMessage: z.ZodType = z.discriminatedUnion( id: z.string(), result: z.discriminatedUnion("$type", [ z.object({ $type: z.literal("string"), value: z.string() }), - z.object({ $type: z.literal("json"), value: z.string() }), z.object({ $type: z.literal("boolean"), value: z.boolean() }), + z.object({ $type: z.literal("float"), value: z.number() }), + z.object({ + $type: z.literal("size"), + value: z.object({ + height: z.number(), + scale_factor: z.number(), + width: z.number(), + }), + }), ]), }), z.object({