Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions wework/src/App.apps.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ vi.mock('@tauri-apps/api/window', () => ({
}),
scaleFactor: vi.fn().mockResolvedValue(1),
onResized: vi.fn().mockResolvedValue(vi.fn()),
onScaleChanged: vi.fn().mockResolvedValue(vi.fn()),
}),
}))

Expand Down
1 change: 1 addition & 0 deletions wework/src/App.plugins.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ vi.mock('@tauri-apps/api/window', () => ({
}),
scaleFactor: vi.fn().mockResolvedValue(1),
onResized: vi.fn().mockResolvedValue(vi.fn()),
onScaleChanged: vi.fn().mockResolvedValue(vi.fn()),
}),
}))

Expand Down
65 changes: 1 addition & 64 deletions wework/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,11 @@ import { TelemetryBridge } from '@/telemetry/TelemetryBridge'
import { track, useTelemetryEnabled } from '@/telemetry/client'
import { WorkspaceTabPortalOwner } from '@/components/topnav/TitlebarActionsPortal'
import { setActiveWorkspaceTabPortalOwner } from '@/components/topnav/workspaceTabPortalOwnership'
import { useTauriViewportSize } from '@/hooks/useTauriViewportSize'

const WORKBENCH_STARTUP_REVEAL_TIMEOUT_MS = 6000
const POPOUT_WINDOW_LABEL = 'popout-window'

interface ViewportSize {
width: number
height: number
}

function isPopoutWindowRuntime() {
if (!isTauriRuntime()) return false
try {
Expand Down Expand Up @@ -442,65 +438,6 @@ function browserWorkspaceTabStorageScope(): string {
return `browser:${window.name}`
}

function useTauriViewportSize(isTauri: boolean): ViewportSize | null {
const [viewportSize, setViewportSize] = useState<ViewportSize | null>(() => {
if (!isTauri || window.innerWidth <= 0 || window.innerHeight <= 0) return null
return { width: window.innerWidth, height: window.innerHeight }
})

useEffect(() => {
if (!isTauri) return undefined

const appWindow = getCurrentWindow()
let disposed = false
let unlisten: (() => void) | undefined

const applyPhysicalSize = async (
physicalSize: Awaited<ReturnType<typeof appWindow.innerSize>>
) => {
const scaleFactor = await appWindow.scaleFactor()
const logicalSize = physicalSize.toLogical(scaleFactor)
if (disposed || logicalSize.width <= 0 || logicalSize.height <= 0) return
setViewportSize(current =>
current?.width === logicalSize.width && current.height === logicalSize.height
? current
: { width: logicalSize.width, height: logicalSize.height }
)
}

void appWindow
.innerSize()
.then(applyPhysicalSize)
.catch(error => {
console.error('[Wework] Failed to read the Tauri viewport size:', error)
})

void appWindow
.onResized(({ payload }) => {
void applyPhysicalSize(payload).catch(error => {
console.error('[Wework] Failed to update the Tauri viewport size:', error)
})
})
.then(unlistenFn => {
if (disposed) {
unlistenFn()
return
}
unlisten = unlistenFn
})
.catch(error => {
console.error('[Wework] Failed to listen for Tauri viewport changes:', error)
})

return () => {
disposed = true
unlisten?.()
}
}, [isTauri])

return viewportSize
}

function AppShell() {
const { t } = useTranslation('common')
const { pathname: path, search } = useCurrentLocation()
Expand Down
73 changes: 73 additions & 0 deletions wework/src/App.viewport.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { act, renderHook, waitFor } from '@testing-library/react'
import { beforeEach, expect, test, vi } from 'vitest'
import { useTauriViewportSize } from '@/hooks/useTauriViewportSize'

function physicalSize(width: number, height: number) {
return {
width,
height,
toLogical: (scaleFactor: number) => ({
width: width / scaleFactor,
height: height / scaleFactor,
}),
}
}

const windowMocks = vi.hoisted(() => ({
innerSize: vi.fn(),
scaleFactor: vi.fn(),
onResized: vi.fn(),
onScaleChanged: vi.fn(),
resizeHandler: null as ((event: { payload: ReturnType<typeof physicalSize> }) => void) | null,
}))

vi.mock('@tauri-apps/api/window', () => ({
getCurrentWindow: () => ({
innerSize: windowMocks.innerSize,
scaleFactor: windowMocks.scaleFactor,
onResized: windowMocks.onResized,
onScaleChanged: windowMocks.onScaleChanged,
}),
}))

beforeEach(() => {
windowMocks.resizeHandler = null
windowMocks.innerSize.mockReset().mockResolvedValue(physicalSize(1280, 720))
windowMocks.scaleFactor.mockReset().mockResolvedValue(1)
windowMocks.onResized.mockReset().mockImplementation(async handler => {
windowMocks.resizeHandler = handler
return vi.fn()
})
windowMocks.onScaleChanged.mockReset().mockResolvedValue(vi.fn())
})

test('keeps the latest native viewport size when resize conversions finish out of order', async () => {
const { result } = renderHook(() => useTauriViewportSize(true))

await waitFor(() => {
expect(windowMocks.resizeHandler).not.toBeNull()
expect(result.current).toEqual({ width: 1280, height: 720 })
})

let resolveStaleScaleFactor: ((scaleFactor: number) => void) | undefined
const staleScaleFactor = new Promise<number>(resolve => {
resolveStaleScaleFactor = resolve
})
windowMocks.scaleFactor.mockReturnValueOnce(staleScaleFactor).mockResolvedValueOnce(1)

act(() => {
windowMocks.resizeHandler?.({ payload: physicalSize(1504, 929) })
windowMocks.resizeHandler?.({ payload: physicalSize(1728, 1084) })
})

await waitFor(() => {
expect(result.current).toEqual({ width: 1728, height: 1084 })
})

await act(async () => {
resolveStaleScaleFactor?.(1)
await staleScaleFactor
})

expect(result.current).toEqual({ width: 1728, height: 1084 })
})
77 changes: 77 additions & 0 deletions wework/src/hooks/useTauriViewportSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { getCurrentWindow } from '@tauri-apps/api/window'
import { useEffect, useState } from 'react'

interface ViewportSize {
width: number
height: number
}

export function useTauriViewportSize(isTauri: boolean): ViewportSize | null {
const [viewportSize, setViewportSize] = useState<ViewportSize | null>(() => {
if (!isTauri || window.innerWidth <= 0 || window.innerHeight <= 0) return null
return { width: window.innerWidth, height: window.innerHeight }
})

useEffect(() => {
if (!isTauri) return undefined

const appWindow = getCurrentWindow()
let disposed = false
let latestUpdate = 0
let unlistenResize: (() => void) | undefined
let unlistenScale: (() => void) | undefined

const applyPhysicalSize = async (
physicalSize:
| Awaited<ReturnType<typeof appWindow.innerSize>>
| ReturnType<typeof appWindow.innerSize>,
scaleFactor: number | ReturnType<typeof appWindow.scaleFactor>
) => {
const update = ++latestUpdate
const [resolvedSize, resolvedScaleFactor] = await Promise.all([physicalSize, scaleFactor])
if (disposed || update !== latestUpdate) return

const logicalSize = resolvedSize.toLogical(resolvedScaleFactor)
if (logicalSize.width <= 0 || logicalSize.height <= 0) return
setViewportSize(current =>
current?.width === logicalSize.width && current.height === logicalSize.height
? current
: { width: logicalSize.width, height: logicalSize.height }
)
}

void Promise.all([
appWindow.onResized(({ payload }) => {
void applyPhysicalSize(payload, appWindow.scaleFactor()).catch(error => {
console.error('[Wework] Failed to update the Tauri viewport size:', error)
})
}),
appWindow.onScaleChanged(({ payload }) => {
void applyPhysicalSize(payload.size, payload.scaleFactor)
}),
])
.then(([unlistenResizeFn, unlistenScaleFn]) => {
if (disposed) {
unlistenResizeFn()
unlistenScaleFn()
return
}
unlistenResize = unlistenResizeFn
unlistenScale = unlistenScaleFn
void applyPhysicalSize(appWindow.innerSize(), appWindow.scaleFactor()).catch(error => {
console.error('[Wework] Failed to read the Tauri viewport size:', error)
})
})
.catch(error => {
console.error('[Wework] Failed to listen for Tauri viewport changes:', error)
})

return () => {
disposed = true
unlistenResize?.()
unlistenScale?.()
}
}, [isTauri])

return viewportSize
}
Loading