mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-19 20:09:43 +02:00
fix sizing issues
This commit is contained in:
+112
-89
@@ -13,7 +13,6 @@ import type { ProfileMenuRequest } from '../types/profileMenu'
|
|||||||
import { SCOPE_KINDS, type ScopeKind } from '../types/scope'
|
import { SCOPE_KINDS, type ScopeKind } from '../types/scope'
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null
|
let mainWindow: BrowserWindow | null = null
|
||||||
let currentSettingsHeight = 0
|
|
||||||
let moveInterval: ReturnType<typeof setInterval> | null = null
|
let moveInterval: ReturnType<typeof setInterval> | null = null
|
||||||
let moveStartCursor: { x: number; y: number } | null = null
|
let moveStartCursor: { x: number; y: number } | null = null
|
||||||
let moveStartPosition: number[] | null = null
|
let moveStartPosition: number[] | null = null
|
||||||
@@ -21,6 +20,8 @@ let moveStartPosition: number[] | null = null
|
|||||||
const scopePopoutWindows = new Map<ScopeKind, BrowserWindow>()
|
const scopePopoutWindows = new Map<ScopeKind, BrowserWindow>()
|
||||||
const scopePopoutCloseAllowed = new Set<ScopeKind>()
|
const scopePopoutCloseAllowed = new Set<ScopeKind>()
|
||||||
const popoutBoundsTimers = new Map<ScopeKind, ReturnType<typeof setTimeout>>()
|
const popoutBoundsTimers = new Map<ScopeKind, ReturnType<typeof setTimeout>>()
|
||||||
|
const windowSettingsHeights = new Map<number, number>()
|
||||||
|
const windowSettingsBottomAnchors = new Map<number, number>()
|
||||||
|
|
||||||
const WINDOW_DEFAULTS = {
|
const WINDOW_DEFAULTS = {
|
||||||
width: 900,
|
width: 900,
|
||||||
@@ -48,6 +49,99 @@ function isMainRendererWindow(window: BrowserWindow | null): boolean {
|
|||||||
return window !== null && window === mainWindow
|
return window !== null && window === mainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBaseMinHeight(window: BrowserWindow): number {
|
||||||
|
return isMainRendererWindow(window) ? WINDOW_DEFAULTS.minHeight : POPOUT_DEFAULTS.minHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettingsHeight(window: BrowserWindow | null): number {
|
||||||
|
if (!window) return 0
|
||||||
|
return windowSettingsHeights.get(window.id) ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSettingsHeightForWindow(window: BrowserWindow, height: number): void {
|
||||||
|
const nextHeight = Math.max(0, Math.round(height))
|
||||||
|
if (nextHeight === 0) {
|
||||||
|
windowSettingsHeights.delete(window.id)
|
||||||
|
windowSettingsBottomAnchors.delete(window.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
windowSettingsHeights.set(window.id, nextHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLogicalBounds(window: BrowserWindow, bounds = window.getBounds()): WindowBounds {
|
||||||
|
return {
|
||||||
|
...bounds,
|
||||||
|
height: Math.max(getBaseMinHeight(window), bounds.height - getSettingsHeight(window)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLogicalBounds(window: BrowserWindow, bounds: WindowBounds): void {
|
||||||
|
window.setBounds({
|
||||||
|
...bounds,
|
||||||
|
height: bounds.height + getSettingsHeight(window),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWindowHeight(window: BrowserWindow, bounds: WindowBounds, height: number, y = bounds.y): void {
|
||||||
|
window.setBounds({
|
||||||
|
x: bounds.x,
|
||||||
|
y,
|
||||||
|
width: bounds.width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySettingsHeight(window: BrowserWindow, rawNextHeight: number): void {
|
||||||
|
const currentSettingsHeight = getSettingsHeight(window)
|
||||||
|
const nextSettingsHeight = Math.max(0, Math.round(rawNextHeight))
|
||||||
|
const delta = nextSettingsHeight - currentSettingsHeight
|
||||||
|
const baseMinHeight = getBaseMinHeight(window)
|
||||||
|
const [minW] = window.getMinimumSize()
|
||||||
|
|
||||||
|
window.setMinimumSize(minW, baseMinHeight + nextSettingsHeight)
|
||||||
|
|
||||||
|
if (delta !== 0) {
|
||||||
|
const bounds = window.getBounds()
|
||||||
|
const newHeight = Math.max(baseMinHeight + nextSettingsHeight, bounds.height + delta)
|
||||||
|
const display = screen.getDisplayMatching(bounds)
|
||||||
|
const workArea = display.workArea
|
||||||
|
const workAreaBottom = workArea.y + workArea.height
|
||||||
|
const bottomEdge = bounds.y + newHeight
|
||||||
|
|
||||||
|
if (delta > 0 && bottomEdge > workAreaBottom) {
|
||||||
|
if (!windowSettingsBottomAnchors.has(window.id)) {
|
||||||
|
windowSettingsBottomAnchors.set(window.id, bounds.y + bounds.height)
|
||||||
|
}
|
||||||
|
const newY = Math.max(workArea.y, workAreaBottom - newHeight)
|
||||||
|
setWindowHeight(window, bounds, newHeight, newY)
|
||||||
|
} else if (delta < 0) {
|
||||||
|
const anchoredBottom = windowSettingsBottomAnchors.get(window.id)
|
||||||
|
if (anchoredBottom !== undefined) {
|
||||||
|
const targetY = Math.max(workArea.y, Math.min(anchoredBottom - newHeight, workAreaBottom - newHeight))
|
||||||
|
setWindowHeight(window, bounds, newHeight, targetY)
|
||||||
|
} else {
|
||||||
|
const baseHeight = newHeight - nextSettingsHeight
|
||||||
|
const naturalBottom = bounds.y + baseHeight
|
||||||
|
if (naturalBottom < workAreaBottom) {
|
||||||
|
const maxY = workAreaBottom - newHeight
|
||||||
|
if (bounds.y < maxY) {
|
||||||
|
setWindowHeight(window, bounds, newHeight)
|
||||||
|
} else {
|
||||||
|
setWindowHeight(window, bounds, newHeight, maxY)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setWindowHeight(window, bounds, newHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setWindowHeight(window, bounds, newHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSettingsHeightForWindow(window, nextSettingsHeight)
|
||||||
|
}
|
||||||
|
|
||||||
function sendToRenderer(sender: WebContents, channel: string, ...args: unknown[]): void {
|
function sendToRenderer(sender: WebContents, channel: string, ...args: unknown[]): void {
|
||||||
if (!sender.isDestroyed()) {
|
if (!sender.isDestroyed()) {
|
||||||
sender.send(channel, ...args)
|
sender.send(channel, ...args)
|
||||||
@@ -189,8 +283,11 @@ function createMainWindow(): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
windowSettingsHeights.delete(mainWindow.id)
|
||||||
|
windowSettingsBottomAnchors.delete(mainWindow.id)
|
||||||
|
}
|
||||||
mainWindow = null
|
mainWindow = null
|
||||||
currentSettingsHeight = 0
|
|
||||||
|
|
||||||
for (const kind of SCOPE_KINDS) {
|
for (const kind of SCOPE_KINDS) {
|
||||||
destroyScopePopoutWindow(kind)
|
destroyScopePopoutWindow(kind)
|
||||||
@@ -219,7 +316,7 @@ function emitPopoutBoundsChanged(kind: ScopeKind, window: BrowserWindow): void {
|
|||||||
popoutBoundsTimers.delete(kind)
|
popoutBoundsTimers.delete(kind)
|
||||||
|
|
||||||
if (!mainWindow || mainWindow.isDestroyed() || window.isDestroyed()) return
|
if (!mainWindow || mainWindow.isDestroyed() || window.isDestroyed()) return
|
||||||
const bounds = window.getBounds()
|
const bounds = toLogicalBounds(window)
|
||||||
mainWindow.webContents.send('scope-popout:bounds-changed', kind, bounds)
|
mainWindow.webContents.send('scope-popout:bounds-changed', kind, bounds)
|
||||||
}, 80)
|
}, 80)
|
||||||
|
|
||||||
@@ -293,6 +390,7 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
|
|||||||
}
|
}
|
||||||
|
|
||||||
const popoutWindow = new BrowserWindow(options)
|
const popoutWindow = new BrowserWindow(options)
|
||||||
|
setSettingsHeightForWindow(popoutWindow, 0)
|
||||||
scopePopoutWindows.set(kind, popoutWindow)
|
scopePopoutWindows.set(kind, popoutWindow)
|
||||||
|
|
||||||
popoutWindow.once('ready-to-show', () => {
|
popoutWindow.once('ready-to-show', () => {
|
||||||
@@ -311,6 +409,8 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
|
|||||||
})
|
})
|
||||||
|
|
||||||
popoutWindow.on('closed', () => {
|
popoutWindow.on('closed', () => {
|
||||||
|
windowSettingsHeights.delete(popoutWindow.id)
|
||||||
|
windowSettingsBottomAnchors.delete(popoutWindow.id)
|
||||||
scopePopoutWindows.delete(kind)
|
scopePopoutWindows.delete(kind)
|
||||||
scopePopoutCloseAllowed.delete(kind)
|
scopePopoutCloseAllowed.delete(kind)
|
||||||
|
|
||||||
@@ -348,7 +448,7 @@ function syncScopePopouts(nextState: ScopePopoutSyncStateMap): void {
|
|||||||
|| currentBounds.height !== nextBounds.height
|
|| currentBounds.height !== nextBounds.height
|
||||||
|
|
||||||
if (hasBoundsDelta) {
|
if (hasBoundsDelta) {
|
||||||
popoutWindow.setBounds(nextBounds)
|
applyLogicalBounds(popoutWindow, nextBounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,30 +586,14 @@ function setupIPC(): void {
|
|||||||
const targetWindow = getWindowFromSender(event.sender)
|
const targetWindow = getWindowFromSender(event.sender)
|
||||||
if (!targetWindow) return
|
if (!targetWindow) return
|
||||||
|
|
||||||
if (isMainRendererWindow(targetWindow)) {
|
applyLogicalBounds(targetWindow, bounds)
|
||||||
targetWindow.setBounds({
|
|
||||||
...bounds,
|
|
||||||
height: bounds.height + currentSettingsHeight,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetWindow.setBounds(bounds)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('window:get-bounds', (event) => {
|
ipcMain.handle('window:get-bounds', (event) => {
|
||||||
const targetWindow = getWindowFromSender(event.sender)
|
const targetWindow = getWindowFromSender(event.sender)
|
||||||
if (!targetWindow) return null
|
if (!targetWindow) return null
|
||||||
|
|
||||||
const bounds = targetWindow.getBounds()
|
return toLogicalBounds(targetWindow)
|
||||||
if (!isMainRendererWindow(targetWindow)) {
|
|
||||||
return bounds
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...bounds,
|
|
||||||
height: bounds.height - currentSettingsHeight,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('window:reposition', (event, position: 'top' | 'bottom') => {
|
ipcMain.on('window:reposition', (event, position: 'top' | 'bottom') => {
|
||||||
@@ -530,84 +614,23 @@ function setupIPC(): void {
|
|||||||
|
|
||||||
ipcMain.on('window:expand-settings', (event, panelHeight: number) => {
|
ipcMain.on('window:expand-settings', (event, panelHeight: number) => {
|
||||||
const targetWindow = getWindowFromSender(event.sender)
|
const targetWindow = getWindowFromSender(event.sender)
|
||||||
if (!targetWindow || !isMainRendererWindow(targetWindow)) return
|
if (!targetWindow) return
|
||||||
|
|
||||||
const bounds = targetWindow.getBounds()
|
applySettingsHeight(targetWindow, getSettingsHeight(targetWindow) + panelHeight)
|
||||||
const [minW] = targetWindow.getMinimumSize()
|
|
||||||
const newHeight = bounds.height + panelHeight
|
|
||||||
targetWindow.setMinimumSize(minW, WINDOW_DEFAULTS.minHeight + panelHeight)
|
|
||||||
|
|
||||||
const display = screen.getDisplayMatching(bounds)
|
|
||||||
const workArea = display.workArea
|
|
||||||
const bottomEdge = bounds.y + newHeight
|
|
||||||
if (bottomEdge > workArea.y + workArea.height) {
|
|
||||||
const newY = Math.max(workArea.y, workArea.y + workArea.height - newHeight)
|
|
||||||
targetWindow.setBounds({ x: bounds.x, y: newY, width: bounds.width, height: newHeight })
|
|
||||||
} else {
|
|
||||||
targetWindow.setSize(bounds.width, newHeight, true)
|
|
||||||
}
|
|
||||||
currentSettingsHeight = Math.max(0, currentSettingsHeight + Math.round(panelHeight))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('window:collapse-settings', (event, panelHeight: number) => {
|
ipcMain.on('window:collapse-settings', (event, panelHeight: number) => {
|
||||||
const targetWindow = getWindowFromSender(event.sender)
|
const targetWindow = getWindowFromSender(event.sender)
|
||||||
if (!targetWindow || !isMainRendererWindow(targetWindow)) return
|
if (!targetWindow) return
|
||||||
|
|
||||||
const bounds = targetWindow.getBounds()
|
applySettingsHeight(targetWindow, getSettingsHeight(targetWindow) - panelHeight)
|
||||||
const [minW] = targetWindow.getMinimumSize()
|
|
||||||
const newHeight = Math.max(WINDOW_DEFAULTS.minHeight, bounds.height - panelHeight)
|
|
||||||
targetWindow.setMinimumSize(minW, WINDOW_DEFAULTS.minHeight)
|
|
||||||
|
|
||||||
const display = screen.getDisplayMatching(bounds)
|
|
||||||
const workArea = display.workArea
|
|
||||||
const wasAtBottom = bounds.y + bounds.height >= workArea.y + workArea.height - 10
|
|
||||||
if (wasAtBottom) {
|
|
||||||
const newY = Math.min(bounds.y + panelHeight, workArea.y + workArea.height - newHeight)
|
|
||||||
targetWindow.setBounds({ x: bounds.x, y: newY, width: bounds.width, height: newHeight })
|
|
||||||
} else {
|
|
||||||
targetWindow.setSize(bounds.width, newHeight, true)
|
|
||||||
}
|
|
||||||
currentSettingsHeight = Math.max(0, currentSettingsHeight - Math.round(panelHeight))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('window:set-settings-height', (event, panelHeight: number) => {
|
ipcMain.on('window:set-settings-height', (event, panelHeight: number) => {
|
||||||
const targetWindow = getWindowFromSender(event.sender)
|
const targetWindow = getWindowFromSender(event.sender)
|
||||||
if (!targetWindow || !isMainRendererWindow(targetWindow)) return
|
if (!targetWindow) return
|
||||||
|
|
||||||
const nextHeight = Math.max(0, Math.round(panelHeight))
|
applySettingsHeight(targetWindow, panelHeight)
|
||||||
const delta = nextHeight - currentSettingsHeight
|
|
||||||
const [width, height] = targetWindow.getSize()
|
|
||||||
const [minW] = targetWindow.getMinimumSize()
|
|
||||||
|
|
||||||
targetWindow.setMinimumSize(minW, WINDOW_DEFAULTS.minHeight + nextHeight)
|
|
||||||
if (delta !== 0) {
|
|
||||||
const newHeight = Math.max(WINDOW_DEFAULTS.minHeight, height + delta)
|
|
||||||
const bounds = targetWindow.getBounds()
|
|
||||||
const display = screen.getDisplayMatching(bounds)
|
|
||||||
const workArea = display.workArea
|
|
||||||
const bottomEdge = bounds.y + newHeight
|
|
||||||
if (delta > 0 && bottomEdge > workArea.y + workArea.height) {
|
|
||||||
const newY = Math.max(workArea.y, workArea.y + workArea.height - newHeight)
|
|
||||||
targetWindow.setBounds({ x: bounds.x, y: newY, width, height: newHeight })
|
|
||||||
} else if (delta < 0) {
|
|
||||||
const baseHeight = newHeight - nextHeight
|
|
||||||
const naturalBottom = bounds.y + baseHeight
|
|
||||||
if (naturalBottom < workArea.y + workArea.height) {
|
|
||||||
const maxY = workArea.y + workArea.height - newHeight
|
|
||||||
if (bounds.y < maxY) {
|
|
||||||
targetWindow.setSize(width, newHeight, true)
|
|
||||||
} else {
|
|
||||||
targetWindow.setBounds({ x: bounds.x, y: maxY, width, height: newHeight })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
targetWindow.setSize(width, newHeight, true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
targetWindow.setSize(width, newHeight, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSettingsHeight = nextHeight
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('scope-popout:sync', (event, state: ScopePopoutSyncStateMap) => {
|
ipcMain.on('scope-popout:sync', (event, state: ScopePopoutSyncStateMap) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState, type JSX, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent } from 'react'
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type JSX, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent } from 'react'
|
||||||
import type { ScopePopoutSnapshot } from '../../types/popout'
|
import type { ScopePopoutSnapshot } from '../../types/popout'
|
||||||
import { SCOPE_LABELS, type ScopeKind } from '../../types/scope'
|
import { SCOPE_LABELS, type ScopeKind } from '../../types/scope'
|
||||||
import { DEFAULT_SCOPE_SETTINGS, type ScopeSettings } from '../../types/settings'
|
import { DEFAULT_SCOPE_SETTINGS, type ScopeSettings } from '../../types/settings'
|
||||||
@@ -41,11 +41,12 @@ interface ScopePopoutWindowProps {
|
|||||||
scopeKind: ScopeKind
|
scopeKind: ScopeKind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const POPOUT_SETTINGS_EXPAND_HEIGHT = 260
|
||||||
|
|
||||||
export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps): JSX.Element {
|
export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps): JSX.Element {
|
||||||
const [snapshot, setSnapshot] = useState<ScopePopoutSnapshot<ScopeKind> | null>(null)
|
const [snapshot, setSnapshot] = useState<ScopePopoutSnapshot<ScopeKind> | null>(null)
|
||||||
const [miniSettingsOpen, setMiniSettingsOpen] = useState(false)
|
const [miniSettingsOpen, setMiniSettingsOpen] = useState(false)
|
||||||
const [chromeHeight, setChromeHeight] = useState(0)
|
const prevMiniSettingsOpenRef = useRef(false)
|
||||||
const chromeRef = useRef<HTMLDivElement>(null)
|
|
||||||
const dataSource = useMemo(() => new ScopePopoutDataSource(scopeKind), [scopeKind])
|
const dataSource = useMemo(() => new ScopePopoutDataSource(scopeKind), [scopeKind])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -74,6 +75,7 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
|||||||
|
|
||||||
const effectiveAccent = snapshot?.accent ?? '#38bdf8'
|
const effectiveAccent = snapshot?.accent ?? '#38bdf8'
|
||||||
const effectiveSettings = (snapshot?.settings ?? DEFAULT_SCOPE_SETTINGS[scopeKind]) as ScopeSettings[ScopeKind]
|
const effectiveSettings = (snapshot?.settings ?? DEFAULT_SCOPE_SETTINGS[scopeKind]) as ScopeSettings[ScopeKind]
|
||||||
|
const settingsHeight = miniSettingsOpen ? POPOUT_SETTINGS_EXPAND_HEIGHT : 0
|
||||||
|
|
||||||
const handleUpdateScopeSettings = <K extends ScopeKind>(kind: K, partial: Partial<ScopeSettings[K]>): void => {
|
const handleUpdateScopeSettings = <K extends ScopeKind>(kind: K, partial: Partial<ScopeSettings[K]>): void => {
|
||||||
if (kind !== scopeKind) return
|
if (kind !== scopeKind) return
|
||||||
@@ -121,26 +123,21 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
|||||||
window.electronAPI.stopWindowMove()
|
window.electronAPI.stopWindowMove()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const chrome = chromeRef.current
|
if (miniSettingsOpen && !prevMiniSettingsOpenRef.current) {
|
||||||
if (!chrome) return
|
window.electronAPI.expandSettings(POPOUT_SETTINGS_EXPAND_HEIGHT)
|
||||||
|
} else if (!miniSettingsOpen && prevMiniSettingsOpenRef.current) {
|
||||||
const updateHeight = (): void => {
|
window.electronAPI.collapseSettings(POPOUT_SETTINGS_EXPAND_HEIGHT)
|
||||||
setChromeHeight(chrome.scrollHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHeight()
|
prevMiniSettingsOpenRef.current = miniSettingsOpen
|
||||||
|
}, [miniSettingsOpen])
|
||||||
const observer = typeof ResizeObserver === 'undefined'
|
|
||||||
? null
|
|
||||||
: new ResizeObserver(() => updateHeight())
|
|
||||||
observer?.observe(chrome)
|
|
||||||
|
|
||||||
return () => observer?.disconnect()
|
|
||||||
}, [miniSettingsOpen, snapshot])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
if (prevMiniSettingsOpenRef.current) {
|
||||||
|
window.electronAPI.setSettingsHeight(0)
|
||||||
|
}
|
||||||
window.electronAPI.stopWindowMove()
|
window.electronAPI.stopWindowMove()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
@@ -152,57 +149,77 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
|||||||
onMouseUp={handleAltDragEnd}
|
onMouseUp={handleAltDragEnd}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={chromeRef}
|
className="scope-popout__viewport"
|
||||||
className={[
|
style={{ height: `calc(100vh - ${settingsHeight}px)` }}
|
||||||
'scope-popout__chrome',
|
|
||||||
miniSettingsOpen ? 'is-expanded' : '',
|
|
||||||
].join(' ').trim()}
|
|
||||||
>
|
>
|
||||||
<header className="scope-popout__header">
|
<div
|
||||||
<div className="scope-popout__drag">
|
className={[
|
||||||
<button
|
'scope-popout__chrome',
|
||||||
type="button"
|
miniSettingsOpen ? 'is-expanded' : '',
|
||||||
className="scope-popout__drag-handle"
|
].join(' ').trim()}
|
||||||
onPointerDown={handleDragStart}
|
>
|
||||||
onPointerUp={handleDragEnd}
|
<header className="scope-popout__header">
|
||||||
onPointerCancel={handleDragEnd}
|
<div className="scope-popout__drag">
|
||||||
onLostPointerCapture={handleDragEnd}
|
<button
|
||||||
aria-label="Drag window"
|
type="button"
|
||||||
title="Drag window"
|
className="scope-popout__drag-handle"
|
||||||
>
|
onPointerDown={handleDragStart}
|
||||||
<span className="scope-popout__drag-icon" aria-hidden="true">
|
onPointerUp={handleDragEnd}
|
||||||
<GripIcon />
|
onPointerCancel={handleDragEnd}
|
||||||
</span>
|
onLostPointerCapture={handleDragEnd}
|
||||||
</button>
|
aria-label="Drag window"
|
||||||
<div className="scope-popout__title-group">
|
title="Drag window"
|
||||||
<span className="scope-popout__title">{snapshot?.label ?? SCOPE_LABELS[scopeKind]}</span>
|
>
|
||||||
<span className="scope-popout__subtitle">Detached Scope</span>
|
<span className="scope-popout__drag-icon" aria-hidden="true">
|
||||||
|
<GripIcon />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div className="scope-popout__title-group">
|
||||||
|
<span className="scope-popout__title">{snapshot?.label ?? SCOPE_LABELS[scopeKind]}</span>
|
||||||
|
<span className="scope-popout__subtitle">Detached Scope</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="scope-popout__actions">
|
<div className="scope-popout__actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`scope-popout__button ${miniSettingsOpen ? 'is-active' : ''}`.trim()}
|
className={`scope-popout__button ${miniSettingsOpen ? 'is-active' : ''}`.trim()}
|
||||||
onClick={() => setMiniSettingsOpen((prev) => !prev)}
|
onClick={() => setMiniSettingsOpen((prev) => !prev)}
|
||||||
aria-label="Toggle mini settings"
|
aria-label="Toggle mini settings"
|
||||||
title="Mini settings"
|
title="Mini settings"
|
||||||
>
|
>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="scope-popout__button"
|
className="scope-popout__button"
|
||||||
onClick={() => window.electronAPI.requestScopePopIn(scopeKind)}
|
onClick={() => window.electronAPI.requestScopePopIn(scopeKind)}
|
||||||
aria-label={`Pop in ${SCOPE_LABELS[scopeKind]}`}
|
aria-label={`Pop in ${SCOPE_LABELS[scopeKind]}`}
|
||||||
title={`Pop in ${SCOPE_LABELS[scopeKind]}`}
|
title={`Pop in ${SCOPE_LABELS[scopeKind]}`}
|
||||||
>
|
>
|
||||||
<PopInIcon />
|
<PopInIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
</div>
|
||||||
|
|
||||||
{miniSettingsOpen && (
|
<div className="scope-popout__content">
|
||||||
|
<div className="scope-popout__canvas-region">
|
||||||
|
<ScopeModule
|
||||||
|
scopeKind={scopeKind}
|
||||||
|
lineColor={effectiveAccent}
|
||||||
|
settings={effectiveSettings}
|
||||||
|
dataSource={dataSource}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{miniSettingsOpen && (
|
||||||
|
<div
|
||||||
|
className="scope-popout__settings-region"
|
||||||
|
style={{ height: `${settingsHeight}px` }}
|
||||||
|
>
|
||||||
<div className="scope-popout__settings-panel">
|
<div className="scope-popout__settings-panel">
|
||||||
<ScopeSettingsSection
|
<ScopeSettingsSection
|
||||||
kind={scopeKind}
|
kind={scopeKind}
|
||||||
@@ -210,22 +227,8 @@ export default function ScopePopoutWindow({ scopeKind }: ScopePopoutWindowProps)
|
|||||||
onUpdate={handleUpdateScopeSettings}
|
onUpdate={handleUpdateScopeSettings}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="scope-popout__content"
|
|
||||||
style={miniSettingsOpen ? { paddingTop: `${chromeHeight}px` } : undefined}
|
|
||||||
>
|
|
||||||
<div className="scope-popout__canvas-region">
|
|
||||||
<ScopeModule
|
|
||||||
scopeKind={scopeKind}
|
|
||||||
lineColor={effectiveAccent}
|
|
||||||
settings={effectiveSettings}
|
|
||||||
dataSource={dataSource}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -817,6 +817,13 @@ select {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scope-popout__viewport {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.scope-popout__chrome {
|
.scope-popout__chrome {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -831,7 +838,7 @@ select {
|
|||||||
transition: max-height 160ms ease, opacity 120ms ease, transform 160ms ease;
|
transition: max-height 160ms ease, opacity 120ms ease, transform 160ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scope-popout:hover .scope-popout__chrome,
|
.scope-popout__viewport:hover .scope-popout__chrome,
|
||||||
.scope-popout__chrome:hover,
|
.scope-popout__chrome:hover,
|
||||||
.scope-popout__chrome.is-expanded {
|
.scope-popout__chrome.is-expanded {
|
||||||
max-height: 58px;
|
max-height: 58px;
|
||||||
@@ -840,10 +847,6 @@ select {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scope-popout__chrome.is-expanded {
|
|
||||||
max-height: 420px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scope-popout__header {
|
.scope-popout__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -951,12 +954,12 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scope-popout__settings-panel {
|
.scope-popout__settings-panel {
|
||||||
flex-shrink: 0;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: linear-gradient(180deg, rgba(6, 8, 11, 0.98), rgba(4, 5, 7, 0.98));
|
background: linear-gradient(180deg, rgba(6, 8, 11, 0.98), rgba(4, 5, 7, 0.98));
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
padding: 12px 0 8px;
|
padding: 12px 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scope-popout__settings-panel .settings-scope-section {
|
.scope-popout__settings-panel .settings-scope-section {
|
||||||
@@ -964,12 +967,17 @@ select {
|
|||||||
padding: 0 14px 12px;
|
padding: 0 14px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scope-popout__settings-region {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.scope-popout__content {
|
.scope-popout__content {
|
||||||
flex: 1;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transition: padding-top 160ms ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.scope-popout__canvas-region {
|
.scope-popout__canvas-region {
|
||||||
|
|||||||
Reference in New Issue
Block a user