mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-11-09 15:33:31 +00:00
fix: overflow, add window in fullscreen mode to avoid jittering when opening up select...
add: launchTheme.css for consistent styling accross the whole components spectrum related to LaunchWindow add: correct styles accross the popovers! with unstyled parameters in Popover.tsx because it was needed to make sure its the same
This commit is contained in:
1 parent
d4d5b47661
commit
6df02e847d
18 files changed
+226
-323
No files matched your search
+20
-115
@@ -26,23 +26,11 @@ let countdownWindow: BrowserWindow | null = null;
|
||||
let updateToastWindow: BrowserWindow | null = null;
|
||||
|
||||
const HUD_OVERLAY_SETTINGS_FILE = path.join(USER_DATA_PATH, "hud-overlay-settings.json");
|
||||
const HUD_BOTTOM_CLEARANCE_CM = 3.5;
|
||||
const DIP_PER_INCH = 96;
|
||||
const CM_PER_INCH = 2.54;
|
||||
const HUD_EDGE_MARGIN_DIP = 16;
|
||||
const HUD_SHADOW_BLEED_DIP = 36;
|
||||
const HUD_MIN_WINDOW_WIDTH = 560;
|
||||
const HUD_COMPACT_HEIGHT = 96;
|
||||
const HUD_MIN_EXPANDED_HEIGHT = 520 + HUD_SHADOW_BLEED_DIP;
|
||||
const UPDATE_TOAST_WIDTH = 456;
|
||||
const UPDATE_TOAST_HEIGHT = 252;
|
||||
const UPDATE_TOAST_GAP_DIP = 18;
|
||||
|
||||
let hudOverlayExpanded = false;
|
||||
let hudOverlayCompactWidth = HUD_MIN_WINDOW_WIDTH;
|
||||
let hudOverlayCompactHeight = HUD_COMPACT_HEIGHT;
|
||||
let hudOverlayExpandedHeight = HUD_MIN_EXPANDED_HEIGHT;
|
||||
|
||||
function getEditorWindowQuery(): Record<string, string> {
|
||||
const query: Record<string, string> = {
|
||||
windowType: "editor",
|
||||
@@ -182,61 +170,21 @@ function getHudOverlayDisplay() {
|
||||
return getScreen().getPrimaryDisplay();
|
||||
}
|
||||
|
||||
function getHudOverlayBounds(expanded: boolean) {
|
||||
const { bounds, workArea } = getHudOverlayDisplay();
|
||||
const maxWindowWidth = Math.max(HUD_MIN_WINDOW_WIDTH, workArea.width - HUD_EDGE_MARGIN_DIP * 2);
|
||||
const windowWidth = Math.min(
|
||||
maxWindowWidth,
|
||||
Math.max(HUD_MIN_WINDOW_WIDTH, Math.round(hudOverlayCompactWidth)),
|
||||
);
|
||||
const maxWindowHeight = Math.max(HUD_COMPACT_HEIGHT, workArea.height - HUD_EDGE_MARGIN_DIP * 2);
|
||||
const desiredHeight = expanded
|
||||
? Math.max(HUD_MIN_EXPANDED_HEIGHT, Math.round(hudOverlayExpandedHeight))
|
||||
: Math.max(HUD_COMPACT_HEIGHT, Math.round(hudOverlayCompactHeight));
|
||||
const windowHeight = Math.min(maxWindowHeight, desiredHeight);
|
||||
const bottomClearanceDip = Math.round((HUD_BOTTOM_CLEARANCE_CM / CM_PER_INCH) * DIP_PER_INCH);
|
||||
const screenBottom = bounds.y + bounds.height;
|
||||
const workAreaBottom = workArea.y + workArea.height;
|
||||
const preferredBottom = screenBottom - bottomClearanceDip;
|
||||
const maximumSafeBottom = workAreaBottom - HUD_EDGE_MARGIN_DIP;
|
||||
const windowBottom = Math.min(preferredBottom, maximumSafeBottom);
|
||||
|
||||
const x = Math.floor(workArea.x + (workArea.width - windowWidth) / 2);
|
||||
const y = Math.max(workArea.y + HUD_EDGE_MARGIN_DIP, Math.floor(windowBottom - windowHeight));
|
||||
|
||||
function getHudOverlayBounds() {
|
||||
const { workArea } = getHudOverlayDisplay();
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: windowWidth,
|
||||
height: windowHeight,
|
||||
x: workArea.x,
|
||||
y: workArea.y,
|
||||
width: workArea.width,
|
||||
height: workArea.height,
|
||||
};
|
||||
}
|
||||
|
||||
function applyHudOverlayBounds(expanded: boolean) {
|
||||
function applyHudOverlayBounds() {
|
||||
if (!hudOverlayWindow || hudOverlayWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
hudOverlayExpanded = expanded;
|
||||
|
||||
const computed = getHudOverlayBounds(expanded);
|
||||
|
||||
if (hudUserPosition) {
|
||||
// Resize in-place at the user's dragged position, clamped so the
|
||||
// window stays fully within the current display's work area.
|
||||
const { workArea } = getHudOverlayDisplay();
|
||||
const x = Math.max(
|
||||
workArea.x,
|
||||
Math.min(hudUserPosition.x, workArea.x + workArea.width - computed.width),
|
||||
);
|
||||
const y = Math.max(
|
||||
workArea.y,
|
||||
Math.min(hudUserPosition.y, workArea.y + workArea.height - computed.height),
|
||||
);
|
||||
hudOverlayWindow.setBounds({ x, y, width: computed.width, height: computed.height }, false);
|
||||
} else {
|
||||
hudOverlayWindow.setBounds(computed, false);
|
||||
}
|
||||
hudOverlayWindow.setBounds(getHudOverlayBounds(), false);
|
||||
|
||||
positionUpdateToastWindow();
|
||||
if (!hudOverlayWindow.isVisible()) {
|
||||
@@ -299,9 +247,7 @@ ipcMain.on("hud-overlay-set-ignore-mouse", (_event, ignore: boolean) => {
|
||||
}
|
||||
});
|
||||
|
||||
// When the user drags the HUD, remember their chosen position so that
|
||||
// subsequent size changes (e.g. idle → recording UI swap) resize in-place
|
||||
// instead of snapping back to the default centered location.
|
||||
// Keep compatibility with existing drag IPC/state.
|
||||
let hudUserPosition: { x: number; y: number } | null = null;
|
||||
let hudDragOffset: { x: number; y: number } | null = null;
|
||||
let hudDragLastCursor: { x: number; y: number } | null = null;
|
||||
@@ -315,7 +261,7 @@ ipcMain.on("hud-overlay-drag", (_event, phase: string, screenX: number, screenY:
|
||||
// the HUD appears "stuck". The renderer marks the drag handle as
|
||||
// -webkit-app-region: drag on Linux, letting the OS move the window for us.
|
||||
// The resulting position is captured by the win.on("moved", ...) listener
|
||||
// below so `hudUserPosition` stays in sync for in-place resize.
|
||||
// below so `hudUserPosition` stays in sync.
|
||||
if (process.platform === "linux") {
|
||||
return;
|
||||
}
|
||||
@@ -365,52 +311,19 @@ ipcMain.on("hud-overlay-hide", () => {
|
||||
});
|
||||
|
||||
ipcMain.on("set-hud-overlay-expanded", (_event, expanded: boolean) => {
|
||||
applyHudOverlayBounds(Boolean(expanded));
|
||||
// Compatibility no-op for launcher HUD: window bounds are now fixed to workArea.
|
||||
void expanded;
|
||||
});
|
||||
|
||||
ipcMain.on("set-hud-overlay-compact-width", (_event, width: number) => {
|
||||
if (!Number.isFinite(width)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxWindowWidth = Math.max(
|
||||
HUD_MIN_WINDOW_WIDTH,
|
||||
getHudOverlayDisplay().workArea.width - HUD_EDGE_MARGIN_DIP * 2,
|
||||
);
|
||||
const nextWidth = Math.min(maxWindowWidth, Math.max(HUD_MIN_WINDOW_WIDTH, Math.round(width)));
|
||||
|
||||
if (nextWidth === hudOverlayCompactWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
hudOverlayCompactWidth = nextWidth;
|
||||
applyHudOverlayBounds(hudOverlayExpanded);
|
||||
// Compatibility no-op for launcher HUD: window bounds are now fixed to workArea.
|
||||
void width;
|
||||
});
|
||||
|
||||
ipcMain.on("set-hud-overlay-measured-height", (_event, height: number, expanded: boolean) => {
|
||||
if (!Number.isFinite(height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxWindowHeight = Math.max(
|
||||
HUD_COMPACT_HEIGHT,
|
||||
getHudOverlayDisplay().workArea.height - HUD_EDGE_MARGIN_DIP * 2,
|
||||
);
|
||||
const nextHeight = Math.min(maxWindowHeight, Math.max(HUD_COMPACT_HEIGHT, Math.round(height)));
|
||||
|
||||
if (expanded) {
|
||||
if (nextHeight === hudOverlayExpandedHeight) {
|
||||
return;
|
||||
}
|
||||
hudOverlayExpandedHeight = Math.max(HUD_MIN_EXPANDED_HEIGHT, nextHeight);
|
||||
} else {
|
||||
if (nextHeight === hudOverlayCompactHeight) {
|
||||
return;
|
||||
}
|
||||
hudOverlayCompactHeight = nextHeight;
|
||||
}
|
||||
|
||||
applyHudOverlayBounds(hudOverlayExpanded);
|
||||
// Compatibility no-op for launcher HUD: window bounds are now fixed to workArea.
|
||||
void height;
|
||||
void expanded;
|
||||
});
|
||||
|
||||
ipcMain.handle("get-hud-overlay-capture-protection", () => {
|
||||
@@ -450,17 +363,11 @@ ipcMain.handle("set-hud-overlay-capture-protection", (_event, enabled: boolean)
|
||||
|
||||
export function createHudOverlayWindow(): BrowserWindow {
|
||||
loadHudOverlayCaptureProtectionSetting();
|
||||
const initialBounds = getHudOverlayBounds(false);
|
||||
const initialBounds = getHudOverlayBounds();
|
||||
|
||||
const win = new BrowserWindow({
|
||||
width: initialBounds.width,
|
||||
height: initialBounds.height,
|
||||
minWidth: HUD_MIN_WINDOW_WIDTH,
|
||||
minHeight: HUD_COMPACT_HEIGHT,
|
||||
maxHeight: Math.max(
|
||||
HUD_COMPACT_HEIGHT,
|
||||
getHudOverlayDisplay().workArea.height - HUD_EDGE_MARGIN_DIP * 2,
|
||||
),
|
||||
x: initialBounds.x,
|
||||
y: initialBounds.y,
|
||||
frame: false,
|
||||
@@ -538,9 +445,7 @@ export function createHudOverlayWindow(): BrowserWindow {
|
||||
hudOverlayWindow = win;
|
||||
|
||||
// On Linux the HUD is dragged by the OS via -webkit-app-region (Wayland
|
||||
// forbids client-side positioning). Mirror the resulting bounds into
|
||||
// hudUserPosition so subsequent expand/collapse resizes stay in place
|
||||
// instead of snapping back to the default centered spot.
|
||||
// forbids client-side positioning). Mirror moved bounds into drag state.
|
||||
if (process.platform === "linux") {
|
||||
win.on("moved", () => {
|
||||
if (win.isDestroyed()) return;
|
||||
@@ -569,7 +474,7 @@ export function createHudOverlayWindow(): BrowserWindow {
|
||||
hudUserPosition = null;
|
||||
}
|
||||
}
|
||||
applyHudOverlayBounds(hudOverlayExpanded);
|
||||
applyHudOverlayBounds();
|
||||
};
|
||||
screen.on("display-removed", handleDisplayRemoved);
|
||||
screen.on("display-metrics-changed", handleDisplayMetricsChanged);
|
||||
|
||||
+6
-1
@@ -19,6 +19,7 @@ export default function App() {
|
||||
const type = params.get("windowType") || "";
|
||||
const isMacOS = /mac/i.test(navigator.platform);
|
||||
setWindowType(type);
|
||||
document.documentElement.dataset.windowType = type;
|
||||
|
||||
if (
|
||||
type === "hud-overlay" ||
|
||||
@@ -31,7 +32,11 @@ export default function App() {
|
||||
document.getElementById("root")?.style.setProperty("background", "transparent");
|
||||
}
|
||||
|
||||
if (type === "hud-overlay" || type === "update-toast") {
|
||||
if (type === "hud-overlay") {
|
||||
document.documentElement.classList.add("hud-overlay-window");
|
||||
document.body.classList.add("hud-overlay-window");
|
||||
document.getElementById("root")?.classList.add("hud-overlay-window");
|
||||
} else if (type === "update-toast") {
|
||||
document.documentElement.style.overflow = "visible";
|
||||
document.body.style.overflow = "visible";
|
||||
document.getElementById("root")?.style.setProperty("overflow", "visible");
|
||||
|
||||
@@ -21,6 +21,7 @@ import { SUPPORTED_LOCALES } from "@/i18n/config";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { useScopedT } from "../../contexts/I18nContext";
|
||||
import { AudioLevelMeter } from "../ui/audio-level-meter";
|
||||
import "./launchTheme.css";
|
||||
import { SourceSelector } from "./SourceSelector";
|
||||
import styles from "./LaunchWindow.module.css";
|
||||
|
||||
@@ -123,7 +124,8 @@ function HudPopover({
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={`${styles.menuCard} ${styles.electronNoDrag}`}
|
||||
className={`launch-theme ${styles.menuCard} ${styles.electronNoDrag}`}
|
||||
unstyled
|
||||
side="bottom"
|
||||
align={align}
|
||||
sideOffset={8}
|
||||
|
||||
@@ -104,15 +104,18 @@
|
||||
width: 300px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
background: rgba(22, 22, 30, 0.96);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
border-radius: 14px;
|
||||
background: var(--launch-panel, rgba(34, 40, 56, 0.62));
|
||||
border: 1px solid var(--launch-border, rgba(255, 255, 255, 0.07));
|
||||
border-radius: 16px;
|
||||
backdrop-filter: blur(18px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||||
padding: 8px;
|
||||
margin-top: auto;
|
||||
margin-bottom: 8px;
|
||||
box-shadow:
|
||||
0 12px 32px rgba(0, 0, 0, 0.22),
|
||||
0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
0 16px 40px rgba(0, 0, 0, 0.28),
|
||||
0 2px 12px rgba(0, 0, 0, 0.12),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
pointer-events: auto;
|
||||
animation: menuCardIn 0.18s ease;
|
||||
}
|
||||
@@ -144,7 +147,7 @@
|
||||
.ddLabel {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
color: #6b6b78;
|
||||
color: var(--launch-label, #8f99b1);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 6px 10px 4px;
|
||||
@@ -160,19 +163,20 @@
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 12px;
|
||||
color: #6b6b78;
|
||||
color: var(--launch-text-muted, #aab3c7);
|
||||
cursor: pointer;
|
||||
transition: all 0.12s ease;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ddItem:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: #eeeef2;
|
||||
background: var(--launch-hover, rgba(255, 255, 255, 0.1));
|
||||
color: var(--launch-text, #f3f6ff);
|
||||
}
|
||||
|
||||
.ddItemSelected {
|
||||
color: #3d8bff;
|
||||
background: var(--launch-selected, rgba(92, 153, 255, 0.22));
|
||||
color: var(--launch-accent-soft, #dbeafe);
|
||||
}
|
||||
|
||||
.finalizingState {
|
||||
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
ArrowClockwise as RefreshCw,
|
||||
} from "@phosphor-icons/react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { RxDragHandleDots2 } from "react-icons/rx";
|
||||
import { useScopedT } from "../../contexts/I18nContext";
|
||||
import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices";
|
||||
@@ -42,6 +40,7 @@ import {
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Button } from "../ui/button";
|
||||
import { RecordingControls } from "./RecordingControls";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
interface DesktopSource {
|
||||
id: string;
|
||||
@@ -106,6 +105,7 @@ export function LaunchWindow() {
|
||||
const [showFloatingWebcamPreview, setShowFloatingWebcamPreview] = useState(true);
|
||||
const [webcamPreviewOffset, setWebcamPreviewOffset] = useState(DEFAULT_WEBCAM_PREVIEW_OFFSET);
|
||||
const [recordingHudOffset, setRecordingHudOffset] = useState(DEFAULT_RECORDING_HUD_OFFSET);
|
||||
const [isHudDragging, setIsHudDragging] = useState(false);
|
||||
const [hudOverlayMousePassthroughSupported, setHudOverlayMousePassthroughSupported] = useState<
|
||||
boolean | null
|
||||
>(null);
|
||||
@@ -113,6 +113,8 @@ export function LaunchWindow() {
|
||||
const [appVersion, setAppVersion] = useState<string | null>(null);
|
||||
const hudContentRef = useRef<HTMLDivElement>(null);
|
||||
const hudBarRef = useRef<HTMLDivElement>(null);
|
||||
const hudBarTransformRef = useRef<HTMLDivElement | null>(null);
|
||||
const recordingHudOffsetRef = useRef(DEFAULT_RECORDING_HUD_OFFSET);
|
||||
const webcamPreviewRef = useRef<HTMLVideoElement | null>(null);
|
||||
const recordingWebcamPreviewRef = useRef<HTMLVideoElement | null>(null);
|
||||
const recordingWebcamPreviewContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -132,7 +134,6 @@ export function LaunchWindow() {
|
||||
const hudDragStartRef = useRef<
|
||||
| {
|
||||
pointerId: number;
|
||||
mode: "webcam-preview";
|
||||
startX: number;
|
||||
startY: number;
|
||||
originX: number;
|
||||
@@ -142,18 +143,13 @@ export function LaunchWindow() {
|
||||
hudWidth: number;
|
||||
hudHeight: number;
|
||||
}
|
||||
| {
|
||||
pointerId: number;
|
||||
mode: "overlay";
|
||||
}
|
||||
| null
|
||||
>(null);
|
||||
const isHudDraggingRef = useRef(false);
|
||||
const isWebcamPreviewDraggingRef = useRef(false);
|
||||
const hudDragMoveRafRef = useRef<number | null>(null);
|
||||
const hudDragPendingScreenPositionRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const hudDragPendingPointerRef = useRef<{ clientX: number; clientY: number } | null>(null);
|
||||
|
||||
const [isAtTop, setIsAtTop] = useState(false);
|
||||
const anyPopoverOpen =
|
||||
sourcePopoverOpen ||
|
||||
micPopoverOpen ||
|
||||
@@ -169,18 +165,6 @@ export function LaunchWindow() {
|
||||
setMorePopoverOpen(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const checkPosition = () => {
|
||||
const screenHeight = window.screen.availHeight;
|
||||
const windowY = window.screenY;
|
||||
setIsAtTop(windowY < screenHeight / 2);
|
||||
};
|
||||
checkPosition();
|
||||
if (anyPopoverOpen || projectBrowserOpen) {
|
||||
checkPosition();
|
||||
}
|
||||
}, [anyPopoverOpen, projectBrowserOpen]);
|
||||
|
||||
const showWebcamControls = webcamEnabled && !recording;
|
||||
const showRecordingWebcamPreview =
|
||||
webcamEnabled &&
|
||||
@@ -219,7 +203,6 @@ export function LaunchWindow() {
|
||||
useEffect(() => {
|
||||
if (!webcamEnabled) {
|
||||
setWebcamPreviewOffset(DEFAULT_WEBCAM_PREVIEW_OFFSET);
|
||||
setRecordingHudOffset(DEFAULT_RECORDING_HUD_OFFSET);
|
||||
webcamPreviewDragStartRef.current = null;
|
||||
isWebcamPreviewDraggingRef.current = false;
|
||||
setShowFloatingWebcamPreview(true);
|
||||
@@ -227,10 +210,11 @@ export function LaunchWindow() {
|
||||
}, [webcamEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showRecordingWebcamPreview) {
|
||||
setRecordingHudOffset(DEFAULT_RECORDING_HUD_OFFSET);
|
||||
recordingHudOffsetRef.current = recordingHudOffset;
|
||||
if (!isHudDraggingRef.current && hudBarTransformRef.current) {
|
||||
hudBarTransformRef.current.style.transform = `translate3d(${recordingHudOffset.x}px, ${recordingHudOffset.y}px, 0)`;
|
||||
}
|
||||
}, [showRecordingWebcamPreview]);
|
||||
}, [recordingHudOffset]);
|
||||
|
||||
const handleWebcamPreviewPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button !== 0) {
|
||||
@@ -318,31 +302,23 @@ export function LaunchWindow() {
|
||||
event.preventDefault();
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
isHudDraggingRef.current = true;
|
||||
setIsHudDragging(true);
|
||||
window.electronAPI?.hudOverlaySetIgnoreMouse?.(false);
|
||||
|
||||
if (showRecordingWebcamPreview && hudBarRef.current) {
|
||||
const hudRect = hudBarRef.current.getBoundingClientRect();
|
||||
hudDragStartRef.current = {
|
||||
pointerId: event.pointerId,
|
||||
mode: "webcam-preview",
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
originX: recordingHudOffset.x,
|
||||
originY: recordingHudOffset.y,
|
||||
initialLeft: hudRect.left,
|
||||
initialTop: hudRect.top,
|
||||
hudWidth: hudRect.width,
|
||||
hudHeight: hudRect.height,
|
||||
};
|
||||
if (!hudBarRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hudRect = hudBarRef.current.getBoundingClientRect();
|
||||
hudDragStartRef.current = {
|
||||
pointerId: event.pointerId,
|
||||
mode: "overlay",
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
originX: recordingHudOffsetRef.current.x,
|
||||
originY: recordingHudOffsetRef.current.y,
|
||||
initialLeft: hudRect.left,
|
||||
initialTop: hudRect.top,
|
||||
hudWidth: hudRect.width,
|
||||
hudHeight: hudRect.height,
|
||||
};
|
||||
hudDragPendingScreenPositionRef.current = null;
|
||||
window.electronAPI?.hudOverlayDrag?.("start", event.screenX, event.screenY);
|
||||
};
|
||||
|
||||
const handleHudBarPointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
@@ -351,42 +327,42 @@ export function LaunchWindow() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dragState.mode === "webcam-preview") {
|
||||
const deltaX = event.clientX - dragState.startX;
|
||||
const deltaY = event.clientY - dragState.startY;
|
||||
const viewportWidth = Math.max(window.innerWidth, window.screen?.width ?? 0);
|
||||
const viewportHeight = Math.max(window.innerHeight, window.screen?.height ?? 0);
|
||||
const unclampedLeft = dragState.initialLeft + deltaX;
|
||||
const unclampedTop = dragState.initialTop + deltaY;
|
||||
const clampedLeft = Math.min(
|
||||
Math.max(0, unclampedLeft),
|
||||
Math.max(0, viewportWidth - dragState.hudWidth),
|
||||
);
|
||||
const clampedTop = Math.min(
|
||||
Math.max(0, unclampedTop),
|
||||
Math.max(0, viewportHeight - dragState.hudHeight),
|
||||
);
|
||||
|
||||
setRecordingHudOffset({
|
||||
x: dragState.originX + (clampedLeft - dragState.initialLeft),
|
||||
y: dragState.originY + (clampedTop - dragState.initialTop),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
hudDragPendingScreenPositionRef.current = { x: event.screenX, y: event.screenY };
|
||||
hudDragPendingPointerRef.current = { clientX: event.clientX, clientY: event.clientY };
|
||||
if (hudDragMoveRafRef.current !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
hudDragMoveRafRef.current = requestAnimationFrame(() => {
|
||||
hudDragMoveRafRef.current = null;
|
||||
const position = hudDragPendingScreenPositionRef.current;
|
||||
if (!position) {
|
||||
const latestDragState = hudDragStartRef.current;
|
||||
const pointer = hudDragPendingPointerRef.current;
|
||||
if (!latestDragState || !pointer) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.electronAPI?.hudOverlayDrag?.("move", position.x, position.y);
|
||||
const deltaX = pointer.clientX - latestDragState.startX;
|
||||
const deltaY = pointer.clientY - latestDragState.startY;
|
||||
const viewportWidth = Math.max(window.innerWidth, window.screen?.width ?? 0);
|
||||
const viewportHeight = Math.max(window.innerHeight, window.screen?.height ?? 0);
|
||||
const unclampedLeft = latestDragState.initialLeft + deltaX;
|
||||
const unclampedTop = latestDragState.initialTop + deltaY;
|
||||
const clampedLeft = Math.min(
|
||||
Math.max(0, unclampedLeft),
|
||||
Math.max(0, viewportWidth - latestDragState.hudWidth),
|
||||
);
|
||||
const clampedTop = Math.min(
|
||||
Math.max(0, unclampedTop),
|
||||
Math.max(0, viewportHeight - latestDragState.hudHeight),
|
||||
);
|
||||
|
||||
const nextOffset = {
|
||||
x: latestDragState.originX + (clampedLeft - latestDragState.initialLeft),
|
||||
y: latestDragState.originY + (clampedTop - latestDragState.initialTop),
|
||||
};
|
||||
recordingHudOffsetRef.current = nextOffset;
|
||||
if (hudBarTransformRef.current) {
|
||||
hudBarTransformRef.current.style.transform = `translate3d(${nextOffset.x}px, ${nextOffset.y}px, 0)`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -396,22 +372,17 @@ export function LaunchWindow() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dragState.mode === "overlay") {
|
||||
if (hudDragMoveRafRef.current !== null) {
|
||||
cancelAnimationFrame(hudDragMoveRafRef.current);
|
||||
hudDragMoveRafRef.current = null;
|
||||
}
|
||||
const position = hudDragPendingScreenPositionRef.current;
|
||||
if (position) {
|
||||
window.electronAPI?.hudOverlayDrag?.("move", position.x, position.y);
|
||||
}
|
||||
hudDragPendingScreenPositionRef.current = null;
|
||||
window.electronAPI?.hudOverlayDrag?.("end", 0, 0);
|
||||
if (hudDragMoveRafRef.current !== null) {
|
||||
cancelAnimationFrame(hudDragMoveRafRef.current);
|
||||
hudDragMoveRafRef.current = null;
|
||||
}
|
||||
hudDragPendingPointerRef.current = null;
|
||||
|
||||
hudDragStartRef.current = null;
|
||||
const wasDragging = isHudDraggingRef.current;
|
||||
isHudDraggingRef.current = false;
|
||||
setIsHudDragging(false);
|
||||
setRecordingHudOffset({ ...recordingHudOffsetRef.current });
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
@@ -460,7 +431,8 @@ export function LaunchWindow() {
|
||||
cancelAnimationFrame(hudDragMoveRafRef.current);
|
||||
}
|
||||
hudDragMoveRafRef.current = null;
|
||||
hudDragPendingScreenPositionRef.current = null;
|
||||
hudDragPendingPointerRef.current = null;
|
||||
hudDragStartRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -692,86 +664,6 @@ export function LaunchWindow() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const expanded =
|
||||
anyPopoverOpen || projectBrowserOpen || showRecordingWebcamPreview;
|
||||
window.electronAPI.setHudOverlayExpanded(expanded);
|
||||
|
||||
return () => {
|
||||
window.electronAPI.setHudOverlayExpanded(false);
|
||||
};
|
||||
}, [anyPopoverOpen, projectBrowserOpen, showRecordingWebcamPreview]);
|
||||
|
||||
const reportHudSize = useCallback(() => {
|
||||
const hudContent = hudContentRef.current;
|
||||
const hudBar = hudBarRef.current;
|
||||
if (!hudContent || !hudBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showRecordingWebcamPreview) {
|
||||
const viewportWidth = Math.max(window.innerWidth, window.screen?.width ?? 0);
|
||||
const viewportHeight = Math.max(window.innerHeight, window.screen?.height ?? 0);
|
||||
window.electronAPI.setHudOverlayCompactWidth(Math.ceil(viewportWidth));
|
||||
window.electronAPI.setHudOverlayMeasuredHeight(Math.ceil(viewportHeight), true);
|
||||
return;
|
||||
}
|
||||
|
||||
const hudContentRect = hudContent.getBoundingClientRect();
|
||||
const hudBarRect = hudBar.getBoundingClientRect();
|
||||
const standardWidth = Math.max(
|
||||
hudBarRect.width,
|
||||
hudBar.scrollWidth,
|
||||
hudContentRect.width,
|
||||
hudContent.scrollWidth,
|
||||
);
|
||||
const standardHeight = Math.max(hudContentRect.height, hudContent.scrollHeight);
|
||||
|
||||
window.electronAPI.setHudOverlayCompactWidth(Math.ceil(standardWidth + 24));
|
||||
window.electronAPI.setHudOverlayMeasuredHeight(
|
||||
Math.ceil(standardHeight + 24),
|
||||
anyPopoverOpen || projectBrowserOpen,
|
||||
);
|
||||
}, [anyPopoverOpen, projectBrowserOpen, showRecordingWebcamPreview]);
|
||||
|
||||
useEffect(() => {
|
||||
const hudContent = hudContentRef.current;
|
||||
const hudBar = hudBarRef.current;
|
||||
const previewContainer = recordingWebcamPreviewContainerRef.current;
|
||||
if (!hudContent || !hudBar || typeof ResizeObserver === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
let frameId = 0;
|
||||
const scheduleHudSizeReport = () => {
|
||||
if (frameId !== 0) {
|
||||
cancelAnimationFrame(frameId);
|
||||
}
|
||||
frameId = requestAnimationFrame(() => {
|
||||
frameId = 0;
|
||||
reportHudSize();
|
||||
});
|
||||
};
|
||||
|
||||
scheduleHudSizeReport();
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
scheduleHudSizeReport();
|
||||
});
|
||||
resizeObserver.observe(hudContent);
|
||||
resizeObserver.observe(hudBar);
|
||||
if (previewContainer) {
|
||||
resizeObserver.observe(previewContainer);
|
||||
}
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
if (frameId !== 0) {
|
||||
cancelAnimationFrame(frameId);
|
||||
}
|
||||
};
|
||||
}, [reportHudSize]);
|
||||
|
||||
const fetchSources = useCallback(async () => {
|
||||
if (!window.electronAPI) return;
|
||||
setSourcesLoading(true);
|
||||
@@ -1174,16 +1066,12 @@ export function LaunchWindow() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full flex justify-center bg-transparent overflow-visible ${
|
||||
isAtTop ? "items-start pt-5" : "items-end pb-5"
|
||||
}`}
|
||||
className="w-full flex justify-center bg-transparent overflow-visible items-end pb-5"
|
||||
style={{ height: "100vh" }}
|
||||
>
|
||||
<div
|
||||
ref={hudContentRef}
|
||||
className={`flex items-center overflow-visible ${
|
||||
isAtTop ? "flex-col" : "flex-col-reverse"
|
||||
}`}
|
||||
className="flex items-center overflow-visible flex-col-reverse"
|
||||
onMouseEnter={() => window.electronAPI?.hudOverlaySetIgnoreMouse?.(false)}
|
||||
onMouseLeave={() => {
|
||||
if (
|
||||
@@ -1199,15 +1087,16 @@ export function LaunchWindow() {
|
||||
>
|
||||
<div className="flex flex-col items-center pointer-events-auto">
|
||||
<div
|
||||
ref={hudBarTransformRef}
|
||||
style={{
|
||||
transform: `translate(${recordingHudOffset.x}px, ${recordingHudOffset.y}px)`,
|
||||
transform: `translate3d(${recordingHudOffset.x}px, ${recordingHudOffset.y}px, 0)`,
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
ref={hudBarRef}
|
||||
layout={!showRecordingWebcamPreview}
|
||||
layout={!showRecordingWebcamPreview && !isHudDragging}
|
||||
transition={hudStateTransition}
|
||||
className={`${styles.bar} ${isAtTop ? "mt-2" : "mb-2"}`}
|
||||
className={`${styles.bar} mb-2`}
|
||||
>
|
||||
<div
|
||||
// On Linux (especially Wayland) the compositor owns window
|
||||
@@ -1233,7 +1122,7 @@ export function LaunchWindow() {
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
<motion.div
|
||||
key={hudMode}
|
||||
layout={!showRecordingWebcamPreview}
|
||||
layout={!showRecordingWebcamPreview && !isHudDragging}
|
||||
className={styles.barState}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
|
||||
@@ -1,3 +1,61 @@
|
||||
.source-selector-popover {
|
||||
background: var(--launch-panel, rgba(34, 40, 56, 0.62));
|
||||
border: 1px solid var(--launch-border, rgba(255, 255, 255, 0.07));
|
||||
border-radius: 16px;
|
||||
backdrop-filter: blur(18px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||||
box-shadow:
|
||||
0 16px 40px rgba(0, 0, 0, 0.28),
|
||||
0 2px 12px rgba(0, 0, 0, 0.12),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.source-selector-item {
|
||||
color: var(--launch-text-muted, #aab3c7);
|
||||
}
|
||||
|
||||
.source-selector-item:hover {
|
||||
background: var(--launch-hover, rgba(255, 255, 255, 0.1));
|
||||
color: var(--launch-text, #f3f6ff);
|
||||
}
|
||||
|
||||
.source-selector-item-selected {
|
||||
background: var(--launch-selected, rgba(92, 153, 255, 0.22));
|
||||
color: var(--launch-accent-soft, #dbeafe);
|
||||
}
|
||||
|
||||
.source-selector-thumb-fallback {
|
||||
background: #23232d;
|
||||
}
|
||||
|
||||
.source-selector-text {
|
||||
color: var(--launch-text, #f3f6ff);
|
||||
}
|
||||
|
||||
.source-selector-muted {
|
||||
color: var(--launch-text-muted, #aab3c7);
|
||||
}
|
||||
|
||||
.source-selector-subtle {
|
||||
color: #747483;
|
||||
}
|
||||
|
||||
.source-selector-label {
|
||||
color: var(--launch-label, #8f99b1);
|
||||
}
|
||||
|
||||
.source-selector-dot {
|
||||
background: var(--launch-accent, #3d8bff);
|
||||
}
|
||||
|
||||
.source-selector-dot-inner {
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.source-selector-accent-border {
|
||||
border-color: var(--launch-accent, #3d8bff);
|
||||
}
|
||||
|
||||
/* Custom Scrollbar for Source Selector */
|
||||
.source-selector-scroll::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useScopedT } from "@/contexts/I18nContext";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import "./launchTheme.css";
|
||||
import "./SourceSelector.css";
|
||||
|
||||
interface DesktopSource {
|
||||
@@ -131,12 +132,12 @@ export const SourceSelectorContent = ({
|
||||
// Memoized source item to prevent unnecessary re-renders
|
||||
const SourceItem = useCallback(
|
||||
({ source, isSelected }: { source: DesktopSource; isSelected: boolean }) => (
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"group flex items-center gap-3 px-3 py-2.5 rounded-md transition-colors w-full text-left",
|
||||
"hover:bg-accent hover:text-accent-foreground",
|
||||
isSelected && "bg-accent text-accent-foreground",
|
||||
"source-selector-item group !h-auto w-full justify-start gap-3 rounded-[11px] px-3 py-2.5 text-left font-medium",
|
||||
isSelected && "source-selector-item-selected",
|
||||
)}
|
||||
onClick={() => onSourceSelect(source)}
|
||||
>
|
||||
@@ -146,23 +147,23 @@ export const SourceSelectorContent = ({
|
||||
<img
|
||||
src={source.thumbnail}
|
||||
alt=""
|
||||
className="w-12 h-8 rounded-md object-cover bg-black/50"
|
||||
className="w-12 h-8 rounded-[8px] object-cover bg-black/50"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).style.display = "none";
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-8 rounded-md bg-muted flex items-center justify-center">
|
||||
<div className="source-selector-thumb-fallback w-12 h-8 rounded-[8px] flex items-center justify-center">
|
||||
{source.sourceType === "window" ? (
|
||||
<AppWindow className="w-5 h-5 text-muted-foreground" />
|
||||
<AppWindow className="w-5 h-5 source-selector-muted" />
|
||||
) : (
|
||||
<Monitor className="w-5 h-5 text-muted-foreground" />
|
||||
<Monitor className="w-5 h-5 source-selector-muted" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isSelected && (
|
||||
<div className="absolute -top-0.5 -right-0.5 w-4 h-4 bg-primary rounded-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 bg-primary-foreground rounded-full" />
|
||||
<div className="source-selector-dot absolute -top-0.5 -right-0.5 w-4 h-4 rounded-full flex items-center justify-center">
|
||||
<div className="w-1.5 h-1.5 source-selector-dot-inner rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -172,20 +173,20 @@ export const SourceSelectorContent = ({
|
||||
<div className="flex items-center gap-2 w-full overflow-hidden">
|
||||
<MarqueeText
|
||||
text={source.windowTitle || source.name}
|
||||
className="text-sm font-medium"
|
||||
className="text-sm font-medium source-selector-text"
|
||||
speed={40}
|
||||
/>
|
||||
{source.appName && source.appName !== source.name && (
|
||||
<span className="text-xs text-muted-foreground truncate shrink-0">
|
||||
<span className="text-xs source-selector-muted truncate shrink-0">
|
||||
{source.appName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground truncate w-full text-left">
|
||||
<div className="text-xs source-selector-subtle truncate w-full text-left">
|
||||
{source.sourceType === "screen" ? t("recording.screen") : t("recording.window")}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
),
|
||||
[onSourceSelect, t],
|
||||
);
|
||||
@@ -196,7 +197,7 @@ export const SourceSelectorContent = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="px-3 py-2 text-xs font-medium text-muted-foreground">
|
||||
<div className="px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.08em] source-selector-label">
|
||||
{t("recording.screens")}
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
@@ -218,7 +219,7 @@ export const SourceSelectorContent = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="px-3 py-2 text-xs font-medium text-muted-foreground">
|
||||
<div className="px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.08em] source-selector-label">
|
||||
{t("recording.windows")}
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
@@ -237,7 +238,7 @@ export const SourceSelectorContent = ({
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-primary" />
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 source-selector-accent-border" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -250,7 +251,7 @@ export const SourceSelectorContent = ({
|
||||
{windowSection}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-8 text-sm text-muted-foreground">
|
||||
<div className="text-center py-8 text-sm source-selector-muted">
|
||||
{t("recording.noSourcesFound")}
|
||||
</div>
|
||||
)}
|
||||
@@ -312,7 +313,8 @@ export const SourceSelector = React.memo(function SourceSelector({
|
||||
)}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-80 p-0"
|
||||
className="launch-theme w-80 p-0 source-selector-popover"
|
||||
unstyled
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
side="bottom"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
.launch-theme {
|
||||
--launch-surface: rgba(26, 28, 36, 0.92);
|
||||
--launch-panel: rgba(18, 18, 24, 0.82);
|
||||
--launch-border: rgba(255, 255, 255, 0.07);
|
||||
--launch-border-strong: rgba(255, 255, 255, 0.12);
|
||||
--launch-text: #f3f6ff;
|
||||
--launch-text-muted: #aab3c7;
|
||||
--launch-label: #8f99b1;
|
||||
--launch-hover: rgba(255, 255, 255, 0.1);
|
||||
--launch-selected: rgba(92, 153, 255, 0.22);
|
||||
--launch-accent: #3d8bff;
|
||||
--launch-accent-soft: #dbeafe;
|
||||
}
|
||||
@@ -19,10 +19,12 @@ function PopoverContent({
|
||||
sideOffset = 4,
|
||||
animated = true,
|
||||
usePortal = true,
|
||||
unstyled = false,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content> & {
|
||||
animated?: boolean;
|
||||
usePortal?: boolean;
|
||||
unstyled?: boolean;
|
||||
}) {
|
||||
const content = (
|
||||
<PopoverPrimitive.Content
|
||||
@@ -30,7 +32,9 @@ function PopoverContent({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-md outline-hidden",
|
||||
!unstyled &&
|
||||
"bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-md outline-hidden",
|
||||
unstyled && "z-50 outline-hidden",
|
||||
animated &&
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-popover-content-transform-origin)",
|
||||
className,
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "Close App",
|
||||
"screens": "Screens",
|
||||
"windows": "Windows",
|
||||
"screen": "Screen",
|
||||
"window": "Window",
|
||||
"noSourcesFound": "No sources found",
|
||||
"microphone": "Microphone",
|
||||
"turnOffMicrophone": "Turn Off Microphone",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "Cerrar aplicación",
|
||||
"screens": "Pantallas",
|
||||
"windows": "Ventanas",
|
||||
"screen": "Pantalla",
|
||||
"window": "Ventana",
|
||||
"noSourcesFound": "No se encontraron fuentes",
|
||||
"microphone": "Micrófono",
|
||||
"turnOffMicrophone": "Desactivar micrófono",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "Fermer l’application",
|
||||
"screens": "Écrans",
|
||||
"windows": "Fenêtres",
|
||||
"screen": "Écran",
|
||||
"window": "Fenêtre",
|
||||
"noSourcesFound": "Aucune source trouvée",
|
||||
"microphone": "Microphone",
|
||||
"turnOffMicrophone": "Désactiver le microphone",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "앱 닫기",
|
||||
"screens": "화면",
|
||||
"windows": "창",
|
||||
"screen": "화면",
|
||||
"window": "창",
|
||||
"noSourcesFound": "사용 가능한 소스를 찾을 수 없습니다",
|
||||
"microphone": "마이크",
|
||||
"turnOffMicrophone": "마이크 끄기",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "App sluiten",
|
||||
"screens": "Schermen",
|
||||
"windows": "Vensters",
|
||||
"screen": "Scherm",
|
||||
"window": "Venster",
|
||||
"noSourcesFound": "Geen bronnen gevonden",
|
||||
"microphone": "Microfoon",
|
||||
"turnOffMicrophone": "Microfoon uitschakelen",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "Fechar app",
|
||||
"screens": "Telas",
|
||||
"windows": "Janelas",
|
||||
"screen": "Tela",
|
||||
"window": "Janela",
|
||||
"noSourcesFound": "Nenhuma fonte encontrada",
|
||||
"microphone": "Microfone",
|
||||
"turnOffMicrophone": "Desligar microfone",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "关闭应用",
|
||||
"screens": "屏幕",
|
||||
"windows": "窗口",
|
||||
"screen": "屏幕",
|
||||
"window": "窗口",
|
||||
"noSourcesFound": "未找到源",
|
||||
"microphone": "麦克风",
|
||||
"turnOffMicrophone": "关闭麦克风",
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"closeApp": "關閉應用程式",
|
||||
"screens": "螢幕",
|
||||
"windows": "視窗",
|
||||
"screen": "螢幕",
|
||||
"window": "視窗",
|
||||
"noSourcesFound": "找不到可錄製的來源",
|
||||
"microphone": "麥克風",
|
||||
"turnOffMicrophone": "關閉麥克風",
|
||||
@@ -74,4 +76,4 @@
|
||||
"failedToStart": "無法開始錄製:{{error}}",
|
||||
"failedToStartGeneric": "無法開始錄製"
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -22,6 +22,12 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html.hud-overlay-window,
|
||||
body.hud-overlay-window,
|
||||
#root.hud-overlay-window {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:root {
|
||||
--app-font-sans: "SF Pro Display", "SF Pro Text", Helvetica, sans-serif;
|
||||
--brand-accent: #2563eb;
|
||||
@@ -265,4 +271,3 @@
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user