revamped HUD
This commit is contained in:
@@ -2,16 +2,119 @@
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.hudBar {
|
||||
isolation: isolate;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.electronNoDrag {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.hudBar {
|
||||
isolation: isolate;
|
||||
box-shadow:
|
||||
0 2px 16px rgba(0, 0, 0, 0.25),
|
||||
0 0 40px rgba(100, 80, 200, 0.08);
|
||||
}
|
||||
|
||||
/* Sub-pill group container */
|
||||
.hudGroup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 9999px;
|
||||
padding: 4px 8px;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.hudGroup:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* Icon button within groups */
|
||||
.hudIconBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
border-radius: 9999px;
|
||||
transition: all 0.15s ease;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hudIconBtn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.hudIconBtn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Active icon glow (green) for enabled audio toggles */
|
||||
.hudIconActive {
|
||||
filter: drop-shadow(0 0 4px rgba(74, 222, 128, 0.4));
|
||||
}
|
||||
|
||||
/* Recording pulse animation on the record group */
|
||||
@keyframes recordPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.15);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 16px rgba(239, 68, 68, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.recordingPulse {
|
||||
animation: recordPulse 1.5s ease-in-out infinite;
|
||||
background: rgba(239, 68, 68, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Mic panel above the bar */
|
||||
.micPanel {
|
||||
background: linear-gradient(135deg, rgba(28, 28, 36, 0.97) 0%, rgba(18, 18, 26, 0.96) 100%);
|
||||
backdrop-filter: blur(16px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(140%);
|
||||
border: 1px solid rgba(80, 80, 120, 0.25);
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 2px 12px rgba(0, 0, 0, 0.2),
|
||||
0 0 30px rgba(100, 80, 200, 0.06);
|
||||
animation: micPanelIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
@keyframes micPanelIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Window control buttons */
|
||||
.windowBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px;
|
||||
border-radius: 9999px;
|
||||
transition: all 0.15s ease;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.windowBtn:hover {
|
||||
opacity: 0.9;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* Folder button */
|
||||
.folderButton {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
@@ -27,16 +130,3 @@
|
||||
.folderButton:hover .folderText {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hudOverlayButton {
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.hudOverlayButton:hover {
|
||||
opacity: 0.7;
|
||||
background: none !important;
|
||||
}
|
||||
@@ -4,14 +4,13 @@ import { useScreenRecorder } from "../../hooks/useScreenRecorder";
|
||||
import { useMicrophoneDevices } from "../../hooks/useMicrophoneDevices";
|
||||
import { useAudioLevelMeter } from "../../hooks/useAudioLevelMeter";
|
||||
import { AudioLevelMeter } from "../ui/audio-level-meter";
|
||||
import { Button } from "../ui/button";
|
||||
import { BsRecordCircle } from "react-icons/bs";
|
||||
import { FaRegStopCircle } from "react-icons/fa";
|
||||
import { MdMonitor, MdMic, MdMicOff, MdVolumeUp, MdVolumeOff } from "react-icons/md";
|
||||
import { RxDragHandleDots2 } from "react-icons/rx";
|
||||
import { FaFolderMinus } from "react-icons/fa6";
|
||||
import { FiMinus, FiX } from "react-icons/fi";
|
||||
import { ContentClamp } from "../ui/content-clamp";
|
||||
|
||||
|
||||
export function LaunchWindow() {
|
||||
const { recording, toggleRecording, microphoneEnabled, setMicrophoneEnabled, microphoneDeviceId, setMicrophoneDeviceId, systemAudioEnabled, setSystemAudioEnabled } = useScreenRecorder();
|
||||
@@ -73,7 +72,7 @@ export function LaunchWindow() {
|
||||
};
|
||||
|
||||
checkSelectedSource();
|
||||
|
||||
|
||||
const interval = setInterval(checkSelectedSource, 500);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
@@ -86,18 +85,17 @@ export function LaunchWindow() {
|
||||
|
||||
const openVideoFile = async () => {
|
||||
const result = await window.electronAPI.openVideoFilePicker();
|
||||
|
||||
|
||||
if (result.canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (result.success && result.path) {
|
||||
await window.electronAPI.setCurrentVideoPath(result.path);
|
||||
await window.electronAPI.switchToEditor();
|
||||
}
|
||||
};
|
||||
|
||||
// IPC events for hide/close
|
||||
const sendHudOverlayHide = () => {
|
||||
if (window.electronAPI && window.electronAPI.hudOverlayHide) {
|
||||
window.electronAPI.hudOverlayHide();
|
||||
@@ -117,26 +115,17 @@ export function LaunchWindow() {
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex items-end justify-center bg-transparent">
|
||||
<div
|
||||
className={`w-full max-w-[500px] mx-auto flex flex-col px-4 py-2 ${styles.electronDrag} ${styles.hudBar}`}
|
||||
style={{
|
||||
borderRadius: 16,
|
||||
background: 'linear-gradient(135deg, rgba(28,28,36,0.97) 0%, rgba(18,18,26,0.96) 100%)',
|
||||
backdropFilter: 'blur(16px) saturate(140%)',
|
||||
WebkitBackdropFilter: 'blur(16px) saturate(140%)',
|
||||
border: '1px solid rgba(80,80,120,0.25)',
|
||||
minHeight: 44,
|
||||
}}
|
||||
>
|
||||
<div className={`flex flex-col items-center gap-2 mx-auto ${styles.electronDrag}`}>
|
||||
{/* Mic controls panel */}
|
||||
{showMicControls && (
|
||||
<div className={`flex items-center gap-2 mb-2 pb-2 border-b border-white/10 ${styles.electronNoDrag}`}>
|
||||
<div className={`flex items-center gap-2 px-4 py-2 ${styles.micPanel} ${styles.electronNoDrag}`}>
|
||||
<select
|
||||
value={microphoneDeviceId || selectedDeviceId}
|
||||
onChange={(e) => {
|
||||
setSelectedDeviceId(e.target.value);
|
||||
setMicrophoneDeviceId(e.target.value);
|
||||
}}
|
||||
className="flex-1 bg-white/10 text-white text-xs rounded px-2 py-1 border border-white/20 outline-none truncate"
|
||||
className="flex-1 bg-white/10 text-white text-xs rounded-full px-3 py-1 border border-white/20 outline-none truncate"
|
||||
style={{ maxWidth: '70%' }}
|
||||
>
|
||||
{devices.map((device) => (
|
||||
@@ -149,107 +138,105 @@ export function LaunchWindow() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`flex items-center gap-1 ${styles.electronDrag}`}> <RxDragHandleDots2 size={18} className="text-white/40" /> </div>
|
||||
{/* Main pill bar */}
|
||||
<div
|
||||
className={`flex items-center gap-1.5 px-2 py-1.5 ${styles.hudBar}`}
|
||||
style={{
|
||||
borderRadius: 9999,
|
||||
background: 'linear-gradient(135deg, rgba(28,28,36,0.97) 0%, rgba(18,18,26,0.96) 100%)',
|
||||
backdropFilter: 'blur(16px) saturate(140%)',
|
||||
WebkitBackdropFilter: 'blur(16px) saturate(140%)',
|
||||
border: '1px solid rgba(80,80,120,0.25)',
|
||||
}}
|
||||
>
|
||||
{/* Drag handle */}
|
||||
<div className={`flex items-center px-1 ${styles.electronDrag}`}>
|
||||
<RxDragHandleDots2 size={16} className="text-white/30" />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-left text-xs ${styles.electronNoDrag}`}
|
||||
{/* Source selector */}
|
||||
<button
|
||||
className={`${styles.hudGroup} ${styles.electronNoDrag}`}
|
||||
onClick={openSourceSelector}
|
||||
disabled={recording}
|
||||
title={selectedSource}
|
||||
>
|
||||
<MdMonitor size={14} className="text-white" />
|
||||
<ContentClamp truncateLength={6}>{selectedSource}</ContentClamp>
|
||||
</Button>
|
||||
<MdMonitor size={14} className="text-white/80" />
|
||||
<span className="text-white/70 text-[11px] max-w-[72px] truncate">{selectedSource}</span>
|
||||
</button>
|
||||
|
||||
<div className="w-px h-6 bg-white/30" />
|
||||
{/* Audio controls group */}
|
||||
<div className={`${styles.hudGroup} ${styles.electronNoDrag}`}>
|
||||
<button
|
||||
className={`${styles.hudIconBtn} ${systemAudioEnabled ? styles.hudIconActive : ''}`}
|
||||
onClick={() => !recording && setSystemAudioEnabled(!systemAudioEnabled)}
|
||||
disabled={recording}
|
||||
title={systemAudioEnabled ? "Disable system audio" : "Enable system audio"}
|
||||
>
|
||||
{systemAudioEnabled ? (
|
||||
<MdVolumeUp size={15} className="text-green-400" />
|
||||
) : (
|
||||
<MdVolumeOff size={15} className="text-white/40" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.hudIconBtn} ${microphoneEnabled ? styles.hudIconActive : ''}`}
|
||||
onClick={toggleMicrophone}
|
||||
disabled={recording}
|
||||
title={microphoneEnabled ? "Disable microphone" : "Enable microphone"}
|
||||
>
|
||||
{microphoneEnabled ? (
|
||||
<MdMic size={15} className="text-green-400" />
|
||||
) : (
|
||||
<MdMicOff size={15} className="text-white/40" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
onClick={() => !recording && setSystemAudioEnabled(!systemAudioEnabled)}
|
||||
disabled={recording}
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-1 text-xs ${styles.electronNoDrag}`}
|
||||
title={systemAudioEnabled ? "Disable system audio" : "Enable system audio"}
|
||||
>
|
||||
{systemAudioEnabled ? (
|
||||
<MdVolumeUp size={16} className="text-green-400" />
|
||||
) : (
|
||||
<MdVolumeOff size={16} className="text-white/50" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
onClick={toggleMicrophone}
|
||||
disabled={recording}
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-1 text-xs ${styles.electronNoDrag}`}
|
||||
title={microphoneEnabled ? "Disable microphone" : "Enable microphone"}
|
||||
>
|
||||
{microphoneEnabled ? (
|
||||
<MdMic size={16} className="text-green-400" />
|
||||
) : (
|
||||
<MdMicOff size={16} className="text-white/50" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-6 bg-white/30" />
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
{/* Record/Stop group */}
|
||||
<button
|
||||
className={`${styles.hudGroup} ${styles.electronNoDrag} ${recording ? styles.recordingPulse : ''}`}
|
||||
onClick={hasSelectedSource ? toggleRecording : openSourceSelector}
|
||||
disabled={!hasSelectedSource && !recording}
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-center text-xs ${styles.electronNoDrag}`}
|
||||
style={{ flex: '0 0 auto' }}
|
||||
>
|
||||
{recording ? (
|
||||
<>
|
||||
<FaRegStopCircle size={14} className="text-red-400" />
|
||||
<span className="text-red-400">{formatTime(elapsed)}</span>
|
||||
<FaRegStopCircle size={13} className="text-red-400" />
|
||||
<span className="text-red-400 text-xs font-semibold tabular-nums">{formatTime(elapsed)}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BsRecordCircle size={14} className={hasSelectedSource ? "text-white" : "text-white/50"} />
|
||||
<span className={hasSelectedSource ? "text-white" : "text-white/50"}>Record</span>
|
||||
</>
|
||||
<BsRecordCircle size={14} className={hasSelectedSource ? "text-white/80" : "text-white/30"} />
|
||||
)}
|
||||
</Button>
|
||||
</button>
|
||||
|
||||
<div className="w-px h-6 bg-white/30" />
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
{/* Open file */}
|
||||
<button
|
||||
className={`${styles.hudIconBtn} ${styles.electronNoDrag}`}
|
||||
onClick={openVideoFile}
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-right text-xs ${styles.electronNoDrag} ${styles.folderButton}`}
|
||||
disabled={recording}
|
||||
title="Open video file"
|
||||
>
|
||||
<FaFolderMinus size={14} className="text-white" />
|
||||
<span className={styles.folderText}>Open</span>
|
||||
</Button>
|
||||
<FaFolderMinus size={14} className="text-white/60" />
|
||||
</button>
|
||||
|
||||
<div className="w-px h-6 bg-white/30 mx-2" />
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
className={`ml-2 ${styles.electronNoDrag} hudOverlayButton`}
|
||||
title="Hide HUD"
|
||||
onClick={sendHudOverlayHide}
|
||||
>
|
||||
<FiMinus size={18} style={{ color: '#fff', opacity: 0.7 }} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
className={`ml-1 ${styles.electronNoDrag} hudOverlayButton`}
|
||||
title="Close App"
|
||||
onClick={sendHudOverlayClose}
|
||||
>
|
||||
<FiX size={18} style={{ color: '#fff', opacity: 0.7 }} />
|
||||
</Button>
|
||||
{/* Window controls */}
|
||||
<div className={`flex items-center gap-0.5 ${styles.electronNoDrag}`}>
|
||||
<button
|
||||
className={styles.windowBtn}
|
||||
title="Hide HUD"
|
||||
onClick={sendHudOverlayHide}
|
||||
>
|
||||
<FiMinus size={14} className="text-white" />
|
||||
</button>
|
||||
<button
|
||||
className={styles.windowBtn}
|
||||
title="Close App"
|
||||
onClick={sendHudOverlayClose}
|
||||
>
|
||||
<FiX size={14} className="text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,17 +9,28 @@
|
||||
}
|
||||
|
||||
.sourceCard {
|
||||
border-radius: 10px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(120deg, rgba(38,38,48,0.98) 0%, rgba(24,24,32,0.96) 100%);
|
||||
border: 1px solid rgba(60,60,80,0.22);
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,0.18);
|
||||
transition: box-shadow 0.2s, border 0.2s, background 0.2s;
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sourceCard:hover {
|
||||
border-color: rgba(120,120,160,0.35);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px 0 rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
.selected {
|
||||
border: 2px solid #34B27B;
|
||||
background: linear-gradient(120deg, rgba(91,33,182,0.18) 0%, rgba(38,38,48,0.98) 100%);
|
||||
box-shadow: 0 0 0 2px #34B27B33;
|
||||
background: linear-gradient(120deg, rgba(52,178,123,0.08) 0%, rgba(38,38,48,0.98) 100%);
|
||||
box-shadow: 0 0 12px rgba(52,178,123,0.15), 0 0 4px rgba(52,178,123,0.1);
|
||||
}
|
||||
|
||||
.selected:hover {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -29,8 +40,8 @@
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 0.85rem;
|
||||
color: #f3f4f6;
|
||||
font-size: 0.8rem;
|
||||
color: #e4e4e7;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
@@ -40,6 +51,18 @@
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Checkmark badge */
|
||||
.checkBadge {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: #34B27B;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 8px rgba(52,178,123,0.4);
|
||||
}
|
||||
|
||||
/* scrollbar */
|
||||
.sourceGridScroll {
|
||||
scrollbar-width: thin;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { MdCheck } from "react-icons/md";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import { Card } from "../ui/card";
|
||||
import styles from "./SourceSelector.module.css";
|
||||
|
||||
interface DesktopSource {
|
||||
@@ -60,99 +59,86 @@ export function SourceSelector() {
|
||||
return (
|
||||
<div className={`h-full flex items-center justify-center ${styles.glassContainer}`} style={{ minHeight: '100vh' }}>
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-zinc-600 mx-auto mb-2" />
|
||||
<p className="text-xs text-zinc-300">Loading sources...</p>
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#34B27B] mx-auto mb-2" />
|
||||
<p className="text-xs text-zinc-400">Loading sources...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderSourceCard = (source: DesktopSource) => {
|
||||
const isSelected = selectedSource?.id === source.id;
|
||||
return (
|
||||
<div
|
||||
key={source.id}
|
||||
className={`${styles.sourceCard} ${isSelected ? styles.selected : ''} p-2`}
|
||||
onClick={() => handleSourceSelect(source)}
|
||||
>
|
||||
<div className="relative mb-1.5">
|
||||
<img
|
||||
src={source.thumbnail || ''}
|
||||
alt={source.name}
|
||||
className="w-full aspect-video object-cover rounded-lg"
|
||||
/>
|
||||
{isSelected && (
|
||||
<div className="absolute -top-1.5 -right-1.5">
|
||||
<div className={styles.checkBadge}>
|
||||
<MdCheck size={12} className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{source.appIcon && (
|
||||
<img
|
||||
src={source.appIcon}
|
||||
alt=""
|
||||
className={`${styles.icon} flex-shrink-0`}
|
||||
/>
|
||||
)}
|
||||
<div className={`${styles.name} truncate`}>{source.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen flex flex-col items-center justify-center ${styles.glassContainer}`}>
|
||||
<div className="flex-1 flex flex-col w-full max-w-xl" style={{ padding: 0 }}>
|
||||
<Tabs defaultValue="screens">
|
||||
<TabsList className="grid grid-cols-2 mb-3 bg-zinc-900/40 rounded-full">
|
||||
<TabsTrigger value="screens" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-zinc-200 rounded-full text-xs py-1">Screens</TabsTrigger>
|
||||
<TabsTrigger value="windows" className="data-[state=active]:bg-[#34B27B] data-[state=active]:text-white text-zinc-200 rounded-full text-xs py-1">Windows</TabsTrigger>
|
||||
<div className={`min-h-screen flex flex-col ${styles.glassContainer}`}>
|
||||
<div className="flex-1 flex flex-col w-full px-4 pt-4">
|
||||
<Tabs defaultValue="screens" className="flex-1 flex flex-col">
|
||||
<TabsList className="grid grid-cols-2 mb-3 bg-white/5 rounded-full">
|
||||
<TabsTrigger value="screens" className="data-[state=active]:bg-white/15 data-[state=active]:text-white text-zinc-400 rounded-full text-xs py-1 transition-all">Screens</TabsTrigger>
|
||||
<TabsTrigger value="windows" className="data-[state=active]:bg-white/15 data-[state=active]:text-white text-zinc-400 rounded-full text-xs py-1 transition-all">Windows</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="h-72 flex flex-col justify-stretch">
|
||||
<TabsContent value="screens" className="h-full">
|
||||
<div className={`grid grid-cols-2 gap-2 h-full overflow-y-auto pr-1 relative ${styles.sourceGridScroll}`}>
|
||||
{screenSources.map(source => (
|
||||
<Card
|
||||
key={source.id}
|
||||
className={`${styles.sourceCard} ${selectedSource?.id === source.id ? styles.selected : ''} cursor-pointer h-fit p-2 scale-95`}
|
||||
style={{ margin: 8, width: '90%', maxWidth: 220 }}
|
||||
onClick={() => handleSourceSelect(source)}
|
||||
>
|
||||
<div className="p-1">
|
||||
<div className="relative mb-1">
|
||||
<img
|
||||
src={source.thumbnail || ''}
|
||||
alt={source.name}
|
||||
className="w-full aspect-video object-cover rounded border border-zinc-800"
|
||||
/>
|
||||
{selectedSource?.id === source.id && (
|
||||
<div className="absolute -top-1 -right-1">
|
||||
<div className="w-4 h-4 bg-[#34B27B] rounded-full flex items-center justify-center shadow-md">
|
||||
<MdCheck className={styles.icon} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.name + " truncate"}>{source.name}</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
<div className="flex-1 min-h-0">
|
||||
<TabsContent value="screens" className="h-full mt-0">
|
||||
<div className={`grid grid-cols-2 gap-3 h-[280px] overflow-y-auto pr-1 auto-rows-min ${styles.sourceGridScroll}`}>
|
||||
{screenSources.map(renderSourceCard)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="windows" className="h-full">
|
||||
<div className={`grid grid-cols-2 gap-2 h-full overflow-y-auto pr-1 relative ${styles.sourceGridScroll}`}>
|
||||
{windowSources.map(source => (
|
||||
<Card
|
||||
key={source.id}
|
||||
className={`${styles.sourceCard} ${selectedSource?.id === source.id ? styles.selected : ''} cursor-pointer h-fit p-2 scale-95`}
|
||||
style={{ margin: 8, width: '90%', maxWidth: 220 }}
|
||||
onClick={() => handleSourceSelect(source)}
|
||||
>
|
||||
<div className="p-1">
|
||||
<div className="relative mb-1">
|
||||
<img
|
||||
src={source.thumbnail || ''}
|
||||
alt={source.name}
|
||||
className="w-full aspect-video object-cover rounded border border-gray-700"
|
||||
/>
|
||||
{selectedSource?.id === source.id && (
|
||||
<div className="absolute -top-1 -right-1">
|
||||
<div className="w-4 h-4 bg-blue-600 rounded-full flex items-center justify-center shadow-md">
|
||||
<MdCheck className={styles.icon} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{source.appIcon && (
|
||||
<img
|
||||
src={source.appIcon}
|
||||
alt="App icon"
|
||||
className={styles.icon + " flex-shrink-0"}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.name + " truncate"}>{source.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
<TabsContent value="windows" className="h-full mt-0">
|
||||
<div className={`grid grid-cols-2 gap-3 h-[280px] overflow-y-auto pr-1 auto-rows-min ${styles.sourceGridScroll}`}>
|
||||
{windowSources.map(renderSourceCard)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div className="border-t border-zinc-800 p-2 w-full max-w-xl">
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button variant="outline" onClick={() => window.close()} className="px-4 py-1 text-xs bg-zinc-800 border-zinc-700 text-zinc-200 hover:bg-zinc-700">Cancel</Button>
|
||||
<Button onClick={handleShare} disabled={!selectedSource} className="px-4 py-1 text-xs bg-[#34B27B] text-white hover:bg-[#34B27B]/80 disabled:opacity-50 disabled:bg-zinc-700">Share</Button>
|
||||
</div>
|
||||
<div className="p-3 flex justify-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => window.close()}
|
||||
className="px-5 py-1 text-xs text-zinc-400 hover:text-white hover:bg-white/5 rounded-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleShare}
|
||||
disabled={!selectedSource}
|
||||
className="px-5 py-1 text-xs bg-[#34B27B] text-white hover:bg-[#34B27B]/80 disabled:opacity-30 disabled:bg-zinc-700 rounded-full"
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user