feat: Add a large collection of emoji and other frontend assets, including a sound file, and a backend package.json.
This commit is contained in:
218
Frontend/Electron/src/components/ScreenShareModal.jsx
Normal file
218
Frontend/Electron/src/components/ScreenShareModal.jsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
const ScreenShareModal = ({ onClose, onSelectSource }) => {
|
||||
const [activeTab, setActiveTab] = useState('applications'); // applications | screens | devices
|
||||
const [sources, setSources] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadSources();
|
||||
}, []);
|
||||
|
||||
const loadSources = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// Get screen/window sources from Electron
|
||||
const desktopSources = await window.cryptoAPI.getScreenSources();
|
||||
|
||||
// Get video input devices (webcams)
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const videoDevices = devices.filter(d => d.kind === 'videoinput');
|
||||
|
||||
// Categorize
|
||||
const apps = desktopSources.filter(s => s.id.startsWith('window'));
|
||||
const screens = desktopSources.filter(s => s.id.startsWith('screen'));
|
||||
|
||||
const formattedDevices = videoDevices.map(d => ({
|
||||
id: d.deviceId,
|
||||
name: d.label || `Camera ${d.deviceId.substring(0, 4)}...`,
|
||||
isDevice: true,
|
||||
thumbnail: null // Devices don't have static thumbnails easily referencable without opening stream
|
||||
}));
|
||||
|
||||
setSources({
|
||||
applications: apps,
|
||||
screens: screens,
|
||||
devices: formattedDevices
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to load sources:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (source) => {
|
||||
// If device, pass constraints differently
|
||||
if (source.isDevice) {
|
||||
onSelectSource({ deviceId: source.id, type: 'device' });
|
||||
} else {
|
||||
onSelectSource({ sourceId: source.id, type: 'screen' });
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
const renderGrid = (items) => {
|
||||
if (!items || items.length === 0) return <div style={{ color: '#b9bbbe', padding: 20, textAlign: 'center' }}>No sources found.</div>;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px', padding: '16px' }}>
|
||||
{items.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
onClick={() => handleSelect(item)}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: '4px',
|
||||
padding: '8px',
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'relative', width: '100%', height: '250px', width: '450px', borderRadius: '8px', overflow: 'hidden', marginBottom: '8px' }}>
|
||||
{/* Thumbnail/Placeholder */}
|
||||
{item.thumbnail ? (
|
||||
<img src={item.thumbnail} alt={item.name} style={{ width: '100%', height: '100%', borderRadius: '8px', objectFit: 'cover', display: 'block' }} />
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', backgroundColor: '#202225', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#b9bbbe' }}>
|
||||
📷
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hover Overlay */}
|
||||
<div
|
||||
className="share-overlay"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s'
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.opacity = 1}
|
||||
onMouseLeave={(e) => e.currentTarget.style.opacity = 0}
|
||||
>
|
||||
<button style={{
|
||||
backgroundColor: '#5865F2',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '4px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
boxShadow: '0 4px 6px rgba(0,0,0,0.3)'
|
||||
}}>
|
||||
Share Screen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginTop: '4px', maxWidth: '450px' }}>
|
||||
{item.appIcon && (
|
||||
<img src={item.appIcon} alt="" style={{ width: '20px', height: '20px', marginRight: '8px' }} />
|
||||
)}
|
||||
<div style={{ color: '#dcddde', fontSize: '14px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', fontWeight: '500' }}>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
zIndex: 10000,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}} onClick={onClose}>
|
||||
<style>{`
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
`}</style>
|
||||
<div style={{
|
||||
backgroundColor: '#242429',
|
||||
borderRadius: '8px',
|
||||
width: '965px',
|
||||
height: '740px',
|
||||
maxWidth: '95vw',
|
||||
maxHeight: '95vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: '0 8px 16px rgba(0,0,0,0.24)'
|
||||
}} onClick={e => e.stopPropagation()}>
|
||||
|
||||
{/* Header/Tabs */}
|
||||
<div style={{ display: 'flex', borderBottom: '1px solid #2f3136', padding: '16px 16px 0 16px', flexShrink: 0 }}>
|
||||
<div
|
||||
onClick={() => setActiveTab('applications')}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
cursor: 'pointer',
|
||||
borderBottom: activeTab === 'applications' ? '2px solid white' : '2px solid transparent',
|
||||
color: activeTab === 'applications' ? 'white' : '#b9bbbe',
|
||||
fontWeight: '500'
|
||||
}}
|
||||
>
|
||||
Applications
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setActiveTab('screens')}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
cursor: 'pointer',
|
||||
borderBottom: activeTab === 'screens' ? '2px solid white' : '2px solid transparent',
|
||||
color: activeTab === 'screens' ? 'white' : '#b9bbbe',
|
||||
fontWeight: '500'
|
||||
}}
|
||||
>
|
||||
Screens
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setActiveTab('devices')}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
cursor: 'pointer',
|
||||
borderBottom: activeTab === 'devices' ? '2px solid white' : '2px solid transparent',
|
||||
color: activeTab === 'devices' ? 'white' : '#b9bbbe',
|
||||
fontWeight: '500'
|
||||
}}
|
||||
>
|
||||
Devices
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="no-scrollbar" style={{ flex: 1, overflowY: 'auto' }}>
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: '#b9bbbe' }}>
|
||||
Loading sources...
|
||||
</div>
|
||||
) : (
|
||||
renderGrid(sources[activeTab])
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScreenShareModal;
|
||||
Reference in New Issue
Block a user