snapshot of https://github.com/tauri-apps/tauri.git (dev, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-23 17:00:42 +05:00
commit feda81bf2f
1178 changed files with 156404 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
<script lang="ts">
import { show, hide, setDockVisibility } from '@tauri-apps/api/app'
import type { ViewProps } from '../App.svelte'
let { onMessage }: ViewProps = $props()
let dockVisible = $state(true)
function showApp() {
hideApp()
.then(() => {
setTimeout(() => {
show()
.then(() => onMessage('Shown app'))
.catch(onMessage)
}, 2000)
})
.catch(onMessage)
}
function hideApp() {
return hide()
.then(() => onMessage('Hide app'))
.catch(onMessage)
}
async function toggleDockVisibility() {
await setDockVisibility(!dockVisible)
dockVisible = !dockVisible
}
</script>
<div class="flex gap-2">
<button
class="btn"
id="show"
title="Hides and shows the app after 2 seconds"
onclick={showApp}>Show</button
>
<button class="btn" id="hide" onclick={hideApp}>Hide</button>
<button class="btn" id="toggle-dock-visibility" onclick={toggleDockVisibility}
>Toggle dock visibility</button
>
</div>

View File

@@ -0,0 +1,70 @@
<script lang="ts">
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { Channel, invoke } from '@tauri-apps/api/core'
import { onMount, onDestroy } from 'svelte'
import type { UnlistenFn } from '@tauri-apps/api/event'
import type { ViewProps } from '../App.svelte'
let { onMessage }: ViewProps = $props()
const webviewWindow = getCurrentWebviewWindow()
let unlisten: UnlistenFn | undefined
onMount(async () => {
unlisten = await webviewWindow.listen('rust-event', onMessage)
})
onDestroy(() => {
unlisten?.()
})
function log() {
invoke('log_operation', {
event: 'tauri-click',
payload: 'this payload is optional because we used Option in Rust'
})
}
function performRequest() {
invoke('perform_request', {
endpoint: 'dummy endpoint arg',
body: {
id: 5,
name: 'test'
}
})
.then(onMessage)
.catch(onMessage)
}
function echo() {
invoke('echo', {
message: 'Tauri JSON request!'
})
.then(onMessage)
.catch(onMessage)
invoke('echo', [1, 2, 3]).then(onMessage).catch(onMessage)
}
function spam() {
const channel = new Channel()
channel.onmessage = onMessage
invoke('spam', { channel })
}
function emitEvent() {
webviewWindow.emit('js-event', 'this is the payload string')
}
</script>
<div class="flex gap-2">
<button class="btn" id="log" onclick={log}>Call Log API</button>
<button class="btn" id="request" onclick={performRequest}>
Call Request (async) API
</button>
<button class="btn" id="event" onclick={emitEvent}>
Send event to Rust
</button>
<button class="btn" id="request" onclick={echo}>Echo</button>
<button class="btn" id="request" onclick={spam}>Spam</button>
</div>

View File

@@ -0,0 +1,126 @@
<script lang="ts">
import { Menu, Submenu, NativeIcon } from '@tauri-apps/api/menu'
import MenuBuilder, {
reorderMenuItems,
type Item,
type MenuItemClickDetail,
type MenuItems
} from '../components/MenuBuilder.svelte'
import { defaultWindowIcon } from '@tauri-apps/api/app'
import type { ViewProps } from '../App.svelte'
import { onDestroy } from 'svelte'
import type { Image } from '@tauri-apps/api/image'
let { onMessage }: ViewProps = $props()
let items = $state<Item[]>([])
let menu: Menu | undefined
let popupMenu: Menu | undefined
let submenu: Submenu | undefined
const macOS = navigator.userAgent.includes('Macintosh')
async function createSubmenu(): Promise<Submenu> {
return await Submenu.new({
text: 'app',
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
})
}
async function createSubmenuWithNativeIcon(): Promise<Submenu> {
return await Submenu.new({
text: 'Submenu with NativeIcon',
icon: NativeIcon.Folder,
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
})
}
async function createSubmenuWithImageIcon(): Promise<Submenu> {
let icon: Image | undefined
try {
icon = (await defaultWindowIcon())!
return await Submenu.new({
text: 'Submenu with Image',
icon,
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
})
} finally {
icon?.close()
}
}
async function setMenu(newSubmenu: Submenu) {
menu?.close()
menu = undefined
submenu?.close()
submenu = newSubmenu
menu = await Menu.new({
items: [submenu]
})
await (macOS ? menu.setAsAppMenu() : menu.setAsWindowMenu())
}
async function create() {
await setMenu(await createSubmenu())
}
async function createWithNativeIcon() {
await setMenu(await createSubmenuWithNativeIcon())
}
async function createWithImageIcon() {
await setMenu(await createSubmenuWithImageIcon())
}
async function popup() {
popupMenu?.close()
popupMenu = undefined
popupMenu = await Menu.new({
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
})
await popupMenu.popup()
}
function onItemClick(detail: MenuItemClickDetail) {
onMessage(`Item ${detail.text} clicked`)
}
onDestroy(() => {
menu?.close()
submenu?.close()
popupMenu?.close()
})
</script>
<div class="grid gap-8 mb-4">
<MenuBuilder
bind:items
itemClick={onItemClick}
onItemAdded={async (item) => {
if (item.menu) {
await submenu?.append(item.menu)
}
}}
onItemRemoved={async (item) => {
if (item.menu) {
await submenu?.remove(item.menu)
}
}}
onItemMoved={(item, toIndex) => {
if (submenu) {
reorderMenuItems(submenu, item, toIndex)
}
}}
/>
<div class="flex gap-2">
<button class="btn" onclick={create}>Create menu</button>
<button class="btn" onclick={createWithNativeIcon}
>Create menu with NativeIcon</button
>
<button class="btn" onclick={createWithImageIcon}
>Create menu with Image icon</button
>
<button class="btn" onclick={popup}>Popup</button>
</div>
</div>

View File

@@ -0,0 +1,134 @@
<script lang="ts">
import { TrayIcon } from '@tauri-apps/api/tray'
import MenuBuilder, {
reorderMenuItems,
type Item,
type MenuItemClickDetail,
type MenuItems
} from '../components/MenuBuilder.svelte'
import { Menu } from '@tauri-apps/api/menu'
import type { ViewProps } from '../App.svelte'
import { onDestroy } from 'svelte'
let { onMessage }: ViewProps = $props()
let icon = $state<string>('../../.icons/tray_icon.png')
let tooltip = $state<string>()
let title = $state<string>()
let iconAsTemplate = $state(false)
let menuOnLeftClick = $state(true)
let menuItems = $state<Item[]>([])
let menu: Menu | undefined
let tray = $state<TrayIcon | undefined>()
function onItemClick(detail: MenuItemClickDetail) {
onMessage(`Item ${detail.text} clicked`)
}
async function create() {
try {
menu = await Menu.new({
items: menuItems.map((i) => i.menu).filter(Boolean) as MenuItems[]
})
tray = await TrayIcon.new({
icon,
tooltip,
title,
iconAsTemplate,
menuOnLeftClick,
menu,
action: (event) => onMessage(event)
})
} catch (error) {
menu?.close()
menu = undefined
tray?.close()
tray = undefined
onMessage(error)
}
}
onDestroy(() => {
menu?.close()
tray?.close()
})
</script>
<div class="grid gap-8 mb-4">
<MenuBuilder
bind:items={menuItems}
itemClick={onItemClick}
onItemAdded={async (item) => {
if (item.menu) {
await menu?.append(item.menu)
}
}}
onItemRemoved={async (item) => {
if (item.menu) {
await menu?.remove(item.menu)
}
}}
onItemMoved={(item, toIndex) => {
if (menu) {
reorderMenuItems(menu, item, toIndex)
}
}}
/>
<div class="flex items-center gap-8">
<div class="grid gap-2 grid-cols-3 items-center">
<input
class="input grow"
type="text"
placeholder="Title"
bind:value={title}
/>
<input
class="input grow"
type="text"
placeholder="Tooltip"
bind:value={tooltip}
/>
<label class="flex gap-2">
<input
type="checkbox"
class="checkbox"
bind:checked={menuOnLeftClick}
/>
Menu on left click
</label>
<input
class="input col-span-2"
type="text"
placeholder="Icon path"
bind:value={icon}
/>
<label class="flex gap-2">
<input type="checkbox" class="checkbox" bind:checked={iconAsTemplate} />
Icon as template
</label>
</div>
<div class="flex">
{#if tray}
<button
class="btn"
onclick={() => {
tray?.close()
tray = undefined
}}
title="Remove the tray icon">Remove tray</button
>
{:else}
<button class="btn" onclick={create} title="Creates the tray icon"
>Create tray</button
>
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,72 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte'
import type { ViewProps } from '../App.svelte'
let { onMessage }: ViewProps = $props()
let video: HTMLVideoElement
let mediaStream: MediaStream | undefined
const constraints: MediaStreamConstraints = {
audio: true,
video: true
}
function handleSuccess(stream: MediaStream) {
const settings = stream.getTracks().map((track) => track.getSettings())
onMessage(`Got streams: ${JSON.stringify(settings, null, 2)}`)
const videoTracks = stream.getVideoTracks()
onMessage(`Using video device: ${videoTracks[0]?.label ?? 'Unknown'}`)
// @ts-expect-error
window.stream = mediaStream // make variable available to browser console
if (video) {
video.srcObject = stream
}
}
function handleError(error: unknown) {
if (!(error instanceof DOMException)) {
onMessage(`getUserMedia error: ${error}`)
return
}
if (error.name === 'ConstraintNotSatisfiedError') {
// const v = constraints.video
// onMessage(
// `The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`
// )
onMessage(
`The constraints ${constraints} can not be satisified by your device.`
)
} else if (error.name === 'PermissionDeniedError') {
onMessage(
'Permissions have not been granted to use your camera and '
+ 'microphone, you need to allow the page access to your devices in '
+ 'order for the demo to work.'
)
}
onMessage(`getUserMedia error: ${error}`)
}
onMount(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints)
handleSuccess(stream)
} catch (e) {
handleError(e)
}
})
onDestroy(() => {
for (const track of mediaStream?.getTracks() ?? []) {
track.stop()
}
})
</script>
<div class="flex flex-col gap-2">
<div class="note-red grow">Not available for Linux</div>
<video id="localVideo" autoplay playsinline bind:this={video}>
<track kind="captions" />
</video>
</div>

View File

@@ -0,0 +1,62 @@
<script lang="ts">
import { invoke } from '@tauri-apps/api/core'
import {
getName,
getVersion,
getTauriVersion,
getBundleType,
exit
} from '@tauri-apps/api/app'
import type { ViewProps } from '../App.svelte'
let { onMessage }: ViewProps = $props()
let version = $state('1.0.0')
let tauriVersion = $state('1.0.0')
let appName = $state('Unknown')
let bundleType = $state('Unknown')
getName().then((n) => {
appName = n
})
getVersion().then((v) => {
version = v
})
getTauriVersion().then((v) => {
tauriVersion = v
})
getBundleType().then((b) => {
if (b) {
bundleType = b
}
})
function contextMenu() {
invoke('plugin:app-menu|popup')
}
function exitApp() {
exit().catch(onMessage)
}
</script>
<div class="grid gap-8 justify-items-start">
<p>
This is a demo of Tauri's API capabilities using the <code
>@tauri-apps/api</code
> package. It's used as the main validation app, serving as the test bed of our
development process. In the future, this app will be used on Tauri's integration
tests.
</p>
<pre>
App name: <code>{appName}</code>
App version: <code>{version}</code>
Tauri version: <code>{tauriVersion}</code>
Bundle type: <code>{bundleType}</code>
</pre>
<div class="flex gap-2">
<button class="btn" onclick={contextMenu}>Context menu</button>
<button class="btn" id="exit" onclick={exitApp}>Exit</button>
</div>
</div>

View File

@@ -0,0 +1,996 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import {
LogicalSize,
UserAttentionType,
PhysicalSize,
PhysicalPosition,
Effect,
EffectState,
ProgressBarStatus,
availableMonitors
} from '@tauri-apps/api/window'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import type {
CursorIcon,
Effects,
Monitor,
Theme
} from '@tauri-apps/api/window'
import type { UnlistenFn } from '@tauri-apps/api/event'
import type { ViewProps } from '../App.svelte'
let { onMessage }: ViewProps = $props()
const webview = WebviewWindow.getCurrent()
const webviewMap = $state({
[webview.label]: webview
})
let selectedWebviewLabel = $state(webview.label)
let selectedWebview = $derived(webviewMap[selectedWebviewLabel])
let monitorMap = $state<Record<string, Monitor>>({})
let selectedMonitor = $state('')
availableMonitors().then((monitors) => {
monitorMap = Object.fromEntries(
monitors.map((m, i) => [m.name ?? `Monitor ${i + 1}`, m])
)
})
let focusable = $state(true)
const cursorIconOptions: CursorIcon[] = [
'default',
'crosshair',
'hand',
'arrow',
'move',
'text',
'wait',
'help',
'progress',
// something cannot be done
'notAllowed',
'contextMenu',
'cell',
'verticalText',
'alias',
'copy',
'noDrop',
// something can be grabbed
'grab',
/// something is grabbed
'grabbing',
'allScroll',
'zoomIn',
'zoomOut',
// edge is to be moved
'eResize',
'nResize',
'neResize',
'nwResize',
'sResize',
'seResize',
'swResize',
'wResize',
'ewResize',
'nsResize',
'neswResize',
'nwseResize',
'colResize',
'rowResize'
]
const windowsEffects: Effect[] = [
Effect.Mica,
Effect.Blur,
Effect.Acrylic,
Effect.Tabbed,
Effect.TabbedDark,
Effect.TabbedLight
]
const isWindows = navigator.appVersion.includes('Windows')
const isMacOS = navigator.appVersion.includes('Macintosh')
const effectOptions: Effect[] = isWindows
? windowsEffects
: (Object.values(Effect) as Effect[]).filter(
(e) => !windowsEffects.includes(e)
)
const effectStateOptions = Object.values(EffectState) as EffectState[]
const progressBarStatusOptions = Object.values(ProgressBarStatus)
let newWebviewLabel = $state<string>()
let resizable = $state(true)
let maximizable = $state(true)
let minimizable = $state(true)
let closable = $state(true)
let maximized = $state(false)
let decorations = $state(true)
let alwaysOnTop = $state(false)
let alwaysOnBottom = $state(false)
let contentProtected = $state(false)
let fullscreen = $state(false)
let simpleFullscreen = $state(false)
let width = $state(100)
let height = $state(100)
let minWidth = $state<number | null>(null)
let minHeight = $state<number | null>(null)
let maxWidth = $state<number | null>(null)
let maxHeight = $state<number | null>(null)
let x = $state(0)
let y = $state(0)
let scaleFactor = $state(1)
let innerPosition = $state(new PhysicalPosition(0, 0))
let outerPosition = $state(new PhysicalPosition(0, 0))
let innerSize = $state(new PhysicalSize(0, 0))
let outerSize = $state(new PhysicalSize(0, 0))
// name of the monitor containing the selected window's top-left corner
let monitor = $derived(
Object.entries(monitorMap).find(
([, m]) =>
outerPosition.x >= m.position.x
&& outerPosition.x < m.position.x + m.size.width
&& outerPosition.y >= m.position.y
&& outerPosition.y < m.position.y + m.size.height
)?.[0] ?? ''
)
let resizeEventUnlisten: UnlistenFn | undefined
let moveEventUnlisten: UnlistenFn | undefined
let cursorGrab = $state(false)
let cursorVisible = $state(true)
let cursorX = $state<number | null>(null)
let cursorY = $state<number | null>(null)
let cursorIcon = $state<CursorIcon>('default')
let cursorIgnoreEvents = $state(false)
let windowTitle = $state('Awesome Tauri Example!')
let theme = $state<Theme | 'auto'>('auto')
let effects = $state<Effect[]>([])
let selectedEffect = $state<Effect>()
let effectState = $state<EffectState>()
let effectRadius = $state<number>()
let effectInteractive = $state(false)
let effectR = $state<number>(),
effectG = $state<number>(),
effectB = $state<number>(),
effectA = $state<number>()
let selectedProgressBarStatus = $state<ProgressBarStatus>(
ProgressBarStatus.None
)
let progress = $state(0)
let windowIconPath = $state('')
function setTitle() {
selectedWebview.setTitle(windowTitle)
}
async function hide() {
let visible = await selectedWebview.isVisible()
onMessage('window is ' + (visible ? 'visible' : 'invisible'))
await selectedWebview.hide()
setTimeout(async () => {
visible = await selectedWebview.isVisible()
onMessage('window is ' + (visible ? 'visible' : 'invisible'))
await selectedWebview.show()
visible = await selectedWebview.isVisible()
onMessage('window is ' + (visible ? 'visible' : 'invisible'))
}, 2000)
}
async function disable() {
let enabled = await selectedWebview.isEnabled()
onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
await selectedWebview.setEnabled(false)
setTimeout(async () => {
enabled = await selectedWebview.isEnabled()
onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
await selectedWebview.setEnabled(true)
enabled = await selectedWebview.isEnabled()
onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
}, 2000)
}
function minimize() {
selectedWebview.minimize()
setTimeout(selectedWebview.unminimize, 2000)
}
function changeIcon() {
selectedWebview.setIcon(windowIconPath)
}
function createWebviewWindow() {
if (!newWebviewLabel) return
const label = `main-${newWebviewLabel}`
const webview = new WebviewWindow(label)
webviewMap[label] = webview
webview.once('tauri://error', function (e) {
onMessage('Error creating new webview ' + JSON.stringify(e))
})
webview.once('tauri://created', function () {
onMessage('webview created')
})
}
function loadWindowSize() {
selectedWebview.innerSize().then((response) => {
innerSize = response
width = innerSize.width
height = innerSize.height
})
selectedWebview.outerSize().then((response) => {
outerSize = response
})
}
function loadWindowPosition() {
selectedWebview.innerPosition().then((response) => {
innerPosition = response
})
selectedWebview.outerPosition().then((response) => {
outerPosition = response
x = outerPosition.x
y = outerPosition.y
})
}
async function addWindowEventListeners(window: WebviewWindow | undefined) {
if (!window) return
resizeEventUnlisten?.()
moveEventUnlisten?.()
moveEventUnlisten = await window.listen('tauri://move', loadWindowPosition)
resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)
}
async function requestUserAttention() {
await selectedWebview.minimize()
await selectedWebview.requestUserAttention(UserAttentionType.Critical)
await new Promise((resolve) => setTimeout(resolve, 3000))
await selectedWebview.requestUserAttention(null)
}
async function switchTheme() {
switch (theme) {
case 'dark':
theme = 'light'
break
case 'light':
theme = 'auto'
break
case 'auto':
theme = 'dark'
break
}
await selectedWebview.setTheme(theme === 'auto' ? null : theme)
}
async function updateProgressBar() {
selectedWebview.setProgressBar({
status: selectedProgressBarStatus,
progress
})
}
async function addEffect() {
if (selectedEffect && !effects.includes(selectedEffect)) {
effects = [...effects, selectedEffect]
}
const payload: Effects = {
effects,
state: effectState,
radius: effectRadius,
interactive: effectInteractive
}
if (
Number.isInteger(effectR)
&& Number.isInteger(effectG)
&& Number.isInteger(effectB)
&& Number.isInteger(effectA)
) {
payload.color = [effectR!, effectG!, effectB!, effectA!]
}
// see `:root.window-effects` in app.css
document.documentElement.classList.add('window-effects')
await selectedWebview.clearEffects()
await selectedWebview.setEffects(payload)
}
async function clearEffects() {
effects = []
await selectedWebview.clearEffects()
document.documentElement.classList.remove('window-effects')
}
async function updatePosition() {
selectedWebview.setPosition(new PhysicalPosition(x, y))
}
async function updateSize() {
if (width && height) {
selectedWebview.setSize(new PhysicalSize(width, height))
}
}
function refreshSelectedWebview() {
loadWindowPosition()
loadWindowSize()
selectedWebview.scaleFactor().then((factor) => (scaleFactor = factor))
addWindowEventListeners(selectedWebview)
}
function updateResizable() {
selectedWebview.setResizable(resizable)
}
function updateMaximizable() {
selectedWebview.setMaximizable(maximizable)
}
function updateMinimizable() {
selectedWebview.setMinimizable(minimizable)
}
function updateClosable() {
selectedWebview.setClosable(closable)
}
function updateMaximized() {
maximized ? selectedWebview.maximize() : selectedWebview.unmaximize()
}
function updateDecorations() {
selectedWebview.setDecorations(decorations)
}
function updateAlwaysOnTop() {
selectedWebview.setAlwaysOnTop(alwaysOnTop)
}
function updateAlwaysOnBottom() {
selectedWebview.setAlwaysOnBottom(alwaysOnBottom)
}
function updateContentProtected() {
selectedWebview.setContentProtected(contentProtected)
}
function updateFullscreen() {
selectedWebview.setFullscreen(fullscreen)
}
function updateSimpleFullscreen() {
selectedWebview.setSimpleFullscreen(simpleFullscreen)
}
async function setFullscreenOnMonitor() {
const target = monitorMap[selectedMonitor]
if (!target) return
await selectedWebview.setFullscreenOnMonitor(target.position)
fullscreen = true
}
function updateMinSize() {
minWidth && minHeight
? selectedWebview.setMinSize(new LogicalSize(minWidth, minHeight))
: selectedWebview.setMinSize(null)
}
function updateMaxSize() {
maxWidth && maxHeight && maxWidth > 800 && maxHeight > 400
? selectedWebview.setMaxSize(new LogicalSize(maxWidth, maxHeight))
: selectedWebview.setMaxSize(null)
}
function updateCursorGrab() {
selectedWebview.setCursorGrab(cursorGrab)
}
function updateCursorVisible() {
selectedWebview.setCursorVisible(cursorVisible)
}
function updateCursorIgnoreEvents() {
selectedWebview.setIgnoreCursorEvents(cursorIgnoreEvents)
}
function updateCursorIcon() {
selectedWebview.setCursorIcon(cursorIcon)
}
function updateCursorPosition() {
cursorX !== null
&& cursorY !== null
&& selectedWebview.setCursorPosition(
new PhysicalPosition(cursorX, cursorY)
)
}
$effect(() => {
selectedWebview
refreshSelectedWebview()
})
onDestroy(() => {
resizeEventUnlisten?.()
moveEventUnlisten?.()
})
</script>
<div class="flex flex-col *:grow gap-8 mb-4">
<div
class="flex flex-wrap items-center gap-4 pb-6 border-b-solid border-b-1 border-code"
>
{#if Object.keys(webviewMap).length >= 1}
<div class="grid gap-1">
<h4 class="my-2">Selected Window</h4>
<select
class="input"
bind:value={selectedWebviewLabel}
onchange={refreshSelectedWebview}
>
<option value="" disabled selected>Choose a window...</option>
{#each Object.keys(webviewMap) as label}
<option value={label}>{label}</option>
{/each}
</select>
</div>
{/if}
<div class="grid gap-1">
<h4 class="my-2">Create New Window</h4>
<form
class="flex gap-2"
onsubmit={(ev) => {
createWebviewWindow()
ev.preventDefault()
}}
>
<input
class="input"
type="text"
placeholder="New window label.."
bind:value={newWebviewLabel}
/>
<button class="btn" type="submit">Create</button>
</form>
</div>
</div>
{#if selectedWebview}
<div class="flex flex-wrap items-center gap-4">
<div class="grid gap-1 grow">
<h4 class="my-2">Change Window Icon</h4>
<form
class="flex gap-2"
onsubmit={(ev) => {
changeIcon()
ev.preventDefault()
}}
>
<input
class="input flex-1 min-w-10"
placeholder="Window icon path"
bind:value={windowIconPath}
/>
<button class="btn" type="submit">Change</button>
</form>
</div>
<div class="grid gap-1 grow">
<h4 class="my-2">Set Window Title</h4>
<form
class="flex gap-2"
onsubmit={(ev) => {
setTitle()
ev.preventDefault()
}}
>
<input class="input flex-1 min-w-10" bind:value={windowTitle} />
<button class="btn" type="submit">Set</button>
</form>
</div>
</div>
<div class="flex flex-wrap gap-2">
<button
class="btn"
title="Unminimizes after 2 seconds"
onclick={() => selectedWebview.center()}
>
Center
</button>
<button
class="btn"
title="Unminimizes after 2 seconds"
onclick={minimize}
>
Minimize
</button>
<button class="btn" title="Visible again after 2 seconds" onclick={hide}>
Hide
</button>
<button
class="btn"
title="Enabled again after 2 seconds"
onclick={disable}
>
Disable
</button>
<button
class="btn"
onclick={requestUserAttention}
title="Minimizes the window, requests attention for 3s and then resets it"
>Request attention</button
>
<button class="btn" onclick={switchTheme}>Switch Theme ({theme})</button>
<button
class="btn"
onclick={() => {
focusable = !focusable
selectedWebview.setFocusable(!focusable)
}}
>
Set focusable to {!focusable}
</button>
<form
class="flex gap-2"
onsubmit={(ev) => {
setFullscreenOnMonitor()
ev.preventDefault()
}}
>
<button class="btn" type="submit" disabled={!selectedMonitor}>
Set Fullscreen on Monitor
</button>
<select class="input" bind:value={selectedMonitor}>
<option value="" disabled>Choose a monitor...</option>
{#each Object.keys(monitorMap) as label}
<option value={label}>{label}</option>
{/each}
</select>
</form>
</div>
<div class="grid cols-[repeat(auto-fill,minmax(180px,1fr))] *:flex *:gap-2">
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={resizable}
onchange={updateResizable}
/>
Resizable
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={maximizable}
onchange={updateMaximizable}
/>
Maximizable
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={minimizable}
onchange={updateMinimizable}
/>
Minimizable
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={closable}
onchange={updateClosable}
/>
Closable
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={decorations}
onchange={updateDecorations}
/>
Has decorations
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={alwaysOnTop}
onchange={updateAlwaysOnTop}
/>
Always on top
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={alwaysOnBottom}
onchange={updateAlwaysOnBottom}
/>
Always on bottom
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={contentProtected}
onchange={updateContentProtected}
/>
Content protected
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={maximized}
onchange={updateMaximized}
/>
Maximized
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={fullscreen}
onchange={updateFullscreen}
/>
Fullscreen
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={simpleFullscreen}
onchange={updateSimpleFullscreen}
/>
Simple fullscreen
</label>
</div>
<div class="flex flex-wrap *:flex-basis-30 gap-2">
<div class="grid gap-1 *:grid">
<label>
X
<input
class="input"
type="number"
bind:value={x}
onchange={updatePosition}
min="0"
/>
</label>
<label>
Y
<input
class="input"
type="number"
bind:value={y}
onchange={updatePosition}
min="0"
/>
</label>
</div>
<div class="grid gap-1 *:grid">
<label>
Width
<input
class="input"
type="number"
bind:value={width}
onchange={updateSize}
min="400"
/>
</label>
<div>
Height
<input
class="input"
type="number"
bind:value={height}
onchange={updateSize}
min="400"
/>
</div>
</div>
<div class="grid gap-1 *:grid">
<label>
Min width
<input
class="input"
type="number"
bind:value={minWidth}
onchange={updateMinSize}
/>
</label>
<label>
Min height
<input
class="input"
type="number"
bind:value={minHeight}
onchange={updateMinSize}
/>
</label>
</div>
<div class="grid gap-1 *:grid">
<label>
Max width
<input
class="input"
type="number"
bind:value={maxWidth}
onchange={updateMaxSize}
min="800"
/>
</label>
<label>
Max height
<input
class="input"
type="number"
bind:value={maxHeight}
onchange={updateMaxSize}
min="400"
/>
</label>
</div>
</div>
<div class="grid grid-cols-2 gap-2 max-inline-2xl">
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Inner Size
</div>
<span>Width: {innerSize.width}</span>
<span>Height: {innerSize.height}</span>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Outer Size
</div>
<span>Width: {outerSize.width}</span>
<span>Height: {outerSize.height}</span>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Inner Logical Size
</div>
<span>Width: {innerSize.toLogical(scaleFactor).width.toFixed(3)}</span>
<span>Height: {innerSize.toLogical(scaleFactor).height.toFixed(3)}</span
>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Outer Logical Size
</div>
<span>Width: {outerSize.toLogical(scaleFactor).width.toFixed(3)}</span>
<span>Height: {outerSize.toLogical(scaleFactor).height.toFixed(3)}</span
>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Inner Position
</div>
<span>x: {innerPosition.x}</span>
<span>y: {innerPosition.y}</span>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Outer Position
</div>
<span>x: {outerPosition.x}</span>
<span>y: {outerPosition.y}</span>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Inner Logical Position
</div>
<span>x: {innerPosition.toLogical(scaleFactor).x.toFixed(3)}</span>
<span>y: {innerPosition.toLogical(scaleFactor).y.toFixed(3)}</span>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Outer Logical Position
</div>
<span>x: {outerPosition.toLogical(scaleFactor).x.toFixed(3)}</span>
<span>y: {outerPosition.toLogical(scaleFactor).y.toFixed(3)}</span>
</div>
<div>
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
Current Monitor
</div>
<span>{monitor}</span>
</div>
</div>
<div class="grid gap-2">
<h4 class="my-2">Cursor</h4>
<div class="flex gap-4 *:flex *:gap-2">
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={cursorGrab}
onchange={updateCursorGrab}
/>
Grab
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={cursorVisible}
onchange={updateCursorVisible}
/>
Visible
</label>
<label>
<input
type="checkbox"
class="checkbox"
bind:checked={cursorIgnoreEvents}
onchange={updateCursorIgnoreEvents}
/>
Ignore events
</label>
</div>
<div class="flex gap-2">
<label>
Icon
<select
class="input"
bind:value={cursorIcon}
onchange={updateCursorIcon}
>
{#each cursorIconOptions as kind}
<option value={kind}>{kind}</option>
{/each}
</select>
</label>
<label>
X position
<input
class="input"
type="number"
bind:value={cursorX}
onchange={updateCursorPosition}
/>
</label>
<label>
Y position
<input
class="input"
type="number"
bind:value={cursorY}
onchange={updateCursorPosition}
/>
</label>
</div>
</div>
<div class="flex flex-col gap-1">
<div class="flex gap-2">
<label>
Progress Status
<select
class="input"
bind:value={selectedProgressBarStatus}
onchange={updateProgressBar}
>
{#each progressBarStatusOptions as status}
<option value={status}>{status}</option>
{/each}
</select>
</label>
<label>
Progress
<input
class="input"
type="number"
min="0"
max="100"
bind:value={progress}
onchange={updateProgressBar}
/>
</label>
</div>
</div>
{#if isWindows || isMacOS}
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<div>
Applied effects: {effects.length ? effects.join(', ') : 'None'}
</div>
<button class="btn" onclick={clearEffects}>Clear</button>
</div>
<div class="flex gap-2">
<label>
Effect
<select class="input" bind:value={selectedEffect}>
{#each effectOptions as effect}
<option value={effect}>{effect}</option>
{/each}
</select>
</label>
<label>
State
<select class="input" bind:value={effectState}>
{#each effectStateOptions as state}
<option value={state}>{state}</option>
{/each}
</select>
</label>
<label>
Radius
<input class="input" type="number" bind:value={effectRadius} />
</label>
<label class="flex items-center gap-1">
<input
type="checkbox"
class="checkbox"
bind:checked={effectInteractive}
/>
Interactive
</label>
</div>
<div class="flex">
<label>
Color
<div class="flex gap-2 *:flex-basis-30">
<input
class="input"
type="number"
placeholder="R"
bind:value={effectR}
/>
<input
class="input"
type="number"
placeholder="G"
bind:value={effectG}
/>
<input
class="input"
type="number"
placeholder="B"
bind:value={effectB}
/>
<input
class="input"
type="number"
placeholder="A"
bind:value={effectA}
/>
</div>
</label>
</div>
<div class="flex">
<button class="btn" onclick={addEffect}>Add</button>
</div>
</div>
{/if}
{/if}
</div>