Compare commits
No commits in common. "33f5dc7e7f53ddc1f7fb66898022f45938344847" and "c9c8c008b2efc5d2fb289e34c2c686556f3b9fa9" have entirely different histories.
33f5dc7e7f
...
c9c8c008b2
|
|
@ -1,584 +0,0 @@
|
||||||
import { useState, useEffect, useRef } from 'react';
|
|
||||||
import { GoogleDataService, type GoogleService, type ShareableItem } from '../lib/google';
|
|
||||||
|
|
||||||
interface GoogleExportBrowserProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
onAddToCanvas: (items: ShareableItem[], position: { x: number; y: number }) => void;
|
|
||||||
isDarkMode: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SERVICE_ICONS: Record<GoogleService, string> = {
|
|
||||||
gmail: '📧',
|
|
||||||
drive: '📁',
|
|
||||||
photos: '📷',
|
|
||||||
calendar: '📅',
|
|
||||||
};
|
|
||||||
|
|
||||||
const SERVICE_NAMES: Record<GoogleService, string> = {
|
|
||||||
gmail: 'Gmail',
|
|
||||||
drive: 'Drive',
|
|
||||||
photos: 'Photos',
|
|
||||||
calendar: 'Calendar',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function GoogleExportBrowser({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
onAddToCanvas,
|
|
||||||
isDarkMode,
|
|
||||||
}: GoogleExportBrowserProps) {
|
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [activeTab, setActiveTab] = useState<GoogleService>('gmail');
|
|
||||||
const [items, setItems] = useState<ShareableItem[]>([]);
|
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const [serviceCounts, setServiceCounts] = useState<Record<GoogleService, number>>({
|
|
||||||
gmail: 0,
|
|
||||||
drive: 0,
|
|
||||||
photos: 0,
|
|
||||||
calendar: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dark mode aware colors
|
|
||||||
const colors = isDarkMode ? {
|
|
||||||
bg: '#1a1a1a',
|
|
||||||
cardBg: '#252525',
|
|
||||||
cardBorder: '#404040',
|
|
||||||
text: '#e4e4e4',
|
|
||||||
textMuted: '#a1a1aa',
|
|
||||||
textHeading: '#f4f4f5',
|
|
||||||
hoverBg: '#333333',
|
|
||||||
selectedBg: 'rgba(99, 102, 241, 0.2)',
|
|
||||||
selectedBorder: 'rgba(99, 102, 241, 0.5)',
|
|
||||||
tabActiveBg: '#3b82f6',
|
|
||||||
tabActiveText: '#ffffff',
|
|
||||||
tabInactiveBg: '#333333',
|
|
||||||
tabInactiveText: '#a1a1aa',
|
|
||||||
inputBg: '#333333',
|
|
||||||
inputBorder: '#404040',
|
|
||||||
btnPrimaryBg: '#6366f1',
|
|
||||||
btnPrimaryText: '#ffffff',
|
|
||||||
btnSecondaryBg: '#333333',
|
|
||||||
btnSecondaryText: '#e4e4e4',
|
|
||||||
} : {
|
|
||||||
bg: '#ffffff',
|
|
||||||
cardBg: '#f9fafb',
|
|
||||||
cardBorder: '#e5e7eb',
|
|
||||||
text: '#374151',
|
|
||||||
textMuted: '#6b7280',
|
|
||||||
textHeading: '#1f2937',
|
|
||||||
hoverBg: '#f3f4f6',
|
|
||||||
selectedBg: 'rgba(99, 102, 241, 0.1)',
|
|
||||||
selectedBorder: 'rgba(99, 102, 241, 0.4)',
|
|
||||||
tabActiveBg: '#3b82f6',
|
|
||||||
tabActiveText: '#ffffff',
|
|
||||||
tabInactiveBg: '#f3f4f6',
|
|
||||||
tabInactiveText: '#6b7280',
|
|
||||||
inputBg: '#ffffff',
|
|
||||||
inputBorder: '#e5e7eb',
|
|
||||||
btnPrimaryBg: '#6366f1',
|
|
||||||
btnPrimaryText: '#ffffff',
|
|
||||||
btnSecondaryBg: '#f3f4f6',
|
|
||||||
btnSecondaryText: '#374151',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load items when tab changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) return;
|
|
||||||
|
|
||||||
const loadItems = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
setItems([]);
|
|
||||||
setSelectedIds(new Set());
|
|
||||||
|
|
||||||
try {
|
|
||||||
const service = GoogleDataService.getInstance();
|
|
||||||
const shareService = service.getShareService();
|
|
||||||
|
|
||||||
if (shareService) {
|
|
||||||
const shareableItems = await shareService.listShareableItems(activeTab, 100);
|
|
||||||
setItems(shareableItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also update counts
|
|
||||||
const counts = await service.getStoredCounts();
|
|
||||||
setServiceCounts(counts);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load items:', error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadItems();
|
|
||||||
}, [isOpen, activeTab]);
|
|
||||||
|
|
||||||
// Handle escape key and click outside
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) return;
|
|
||||||
|
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickOutside = (e: MouseEvent) => {
|
|
||||||
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('keydown', handleEscape);
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keydown', handleEscape);
|
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [isOpen, onClose]);
|
|
||||||
|
|
||||||
// Toggle item selection
|
|
||||||
const toggleSelection = (id: string) => {
|
|
||||||
setSelectedIds((prev) => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
if (next.has(id)) {
|
|
||||||
next.delete(id);
|
|
||||||
} else {
|
|
||||||
next.add(id);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Select/deselect all
|
|
||||||
const selectAll = () => {
|
|
||||||
if (selectedIds.size === filteredItems.length) {
|
|
||||||
setSelectedIds(new Set());
|
|
||||||
} else {
|
|
||||||
setSelectedIds(new Set(filteredItems.map((i) => i.id)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter items by search query
|
|
||||||
const filteredItems = items.filter((item) => {
|
|
||||||
if (!searchQuery.trim()) return true;
|
|
||||||
const query = searchQuery.toLowerCase();
|
|
||||||
return (
|
|
||||||
item.title.toLowerCase().includes(query) ||
|
|
||||||
(item.preview && item.preview.toLowerCase().includes(query))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle add to canvas
|
|
||||||
const handleAddToCanvas = () => {
|
|
||||||
const selectedItems = items.filter((i) => selectedIds.has(i.id));
|
|
||||||
if (selectedItems.length === 0) return;
|
|
||||||
|
|
||||||
// Calculate center of viewport for placement
|
|
||||||
const position = { x: 200, y: 200 }; // Default position, will be adjusted by caller
|
|
||||||
onAddToCanvas(selectedItems, position);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format date
|
|
||||||
const formatDate = (timestamp: number) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
const now = new Date();
|
|
||||||
const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
|
|
||||||
if (diffDays === 0) return 'Today';
|
|
||||||
if (diffDays === 1) return 'Yesterday';
|
|
||||||
if (diffDays < 7) return `${diffDays} days ago`;
|
|
||||||
return date.toLocaleDateString();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
||||||
backdropFilter: 'blur(4px)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
zIndex: 100002,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={modalRef}
|
|
||||||
style={{
|
|
||||||
backgroundColor: colors.bg,
|
|
||||||
borderRadius: '12px',
|
|
||||||
width: '90%',
|
|
||||||
maxWidth: '600px',
|
|
||||||
maxHeight: '80vh',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
|
||||||
border: `1px solid ${colors.cardBorder}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Header */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: '16px 20px',
|
|
||||||
borderBottom: `1px solid ${colors.cardBorder}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<span style={{ fontSize: '20px' }}>🔐</span>
|
|
||||||
<h2 style={{ fontSize: '16px', fontWeight: '600', color: colors.textHeading, margin: 0 }}>
|
|
||||||
Your Private Data
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
style={{
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
fontSize: '20px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: colors.textMuted,
|
|
||||||
padding: '4px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Service tabs */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: '8px',
|
|
||||||
padding: '12px 20px',
|
|
||||||
borderBottom: `1px solid ${colors.cardBorder}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(['gmail', 'drive', 'photos', 'calendar'] as GoogleService[]).map((service) => (
|
|
||||||
<button
|
|
||||||
key={service}
|
|
||||||
onClick={() => setActiveTab(service)}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
padding: '8px 12px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
backgroundColor: activeTab === service ? colors.tabActiveBg : colors.tabInactiveBg,
|
|
||||||
color: activeTab === service ? colors.tabActiveText : colors.tabInactiveText,
|
|
||||||
transition: 'all 0.15s ease',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{SERVICE_ICONS[service]}</span>
|
|
||||||
<span>{SERVICE_NAMES[service]}</span>
|
|
||||||
{serviceCounts[service] > 0 && (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: '11px',
|
|
||||||
padding: '2px 6px',
|
|
||||||
borderRadius: '10px',
|
|
||||||
backgroundColor: activeTab === service ? 'rgba(255,255,255,0.2)' : colors.cardBorder,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{serviceCounts[service]}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search and actions */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '12px',
|
|
||||||
padding: '12px 20px',
|
|
||||||
borderBottom: `1px solid ${colors.cardBorder}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ flex: 1, position: 'relative' }}>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: '12px',
|
|
||||||
top: '50%',
|
|
||||||
transform: 'translateY(-50%)',
|
|
||||||
fontSize: '14px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
🔍
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: '8px 12px 8px 36px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: `1px solid ${colors.inputBorder}`,
|
|
||||||
backgroundColor: colors.inputBg,
|
|
||||||
color: colors.text,
|
|
||||||
fontSize: '13px',
|
|
||||||
outline: 'none',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={selectAll}
|
|
||||||
style={{
|
|
||||||
padding: '8px 12px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: `1px solid ${colors.cardBorder}`,
|
|
||||||
backgroundColor: colors.btnSecondaryBg,
|
|
||||||
color: colors.btnSecondaryText,
|
|
||||||
fontSize: '12px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedIds.size === filteredItems.length && filteredItems.length > 0
|
|
||||||
? 'Clear'
|
|
||||||
: 'Select All'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Items list */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
overflowY: 'auto',
|
|
||||||
padding: '8px 12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '40px',
|
|
||||||
color: colors.textMuted,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
) : filteredItems.length === 0 ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '40px',
|
|
||||||
color: colors.textMuted,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ fontSize: '32px', marginBottom: '12px' }}>{SERVICE_ICONS[activeTab]}</span>
|
|
||||||
<p style={{ fontSize: '14px' }}>No {SERVICE_NAMES[activeTab]} data imported yet</p>
|
|
||||||
<a
|
|
||||||
href="/google"
|
|
||||||
style={{ fontSize: '13px', color: '#3b82f6', marginTop: '8px' }}
|
|
||||||
>
|
|
||||||
Import data →
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
||||||
{filteredItems.map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => toggleSelection(item.id)}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '12px',
|
|
||||||
padding: '10px 12px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: selectedIds.has(item.id) ? colors.selectedBg : 'transparent',
|
|
||||||
border: selectedIds.has(item.id)
|
|
||||||
? `1px solid ${colors.selectedBorder}`
|
|
||||||
: '1px solid transparent',
|
|
||||||
transition: 'all 0.15s ease',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
if (!selectedIds.has(item.id)) {
|
|
||||||
e.currentTarget.style.backgroundColor = colors.hoverBg;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
if (!selectedIds.has(item.id)) {
|
|
||||||
e.currentTarget.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Checkbox */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '18px',
|
|
||||||
height: '18px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
border: `2px solid ${selectedIds.has(item.id) ? '#6366f1' : colors.cardBorder}`,
|
|
||||||
backgroundColor: selectedIds.has(item.id) ? '#6366f1' : 'transparent',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedIds.has(item.id) && (
|
|
||||||
<svg
|
|
||||||
width="12"
|
|
||||||
height="12"
|
|
||||||
viewBox="0 0 12 12"
|
|
||||||
fill="none"
|
|
||||||
stroke="white"
|
|
||||||
strokeWidth="2"
|
|
||||||
>
|
|
||||||
<path d="M2 6l3 3 5-6" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
color: colors.text,
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</div>
|
|
||||||
{item.preview && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '12px',
|
|
||||||
color: colors.textMuted,
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
marginTop: '2px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.preview}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Date */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '11px',
|
|
||||||
color: colors.textMuted,
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formatDate(item.date)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: '16px 20px',
|
|
||||||
borderTop: `1px solid ${colors.cardBorder}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ fontSize: '13px', color: colors.textMuted }}>
|
|
||||||
{selectedIds.size > 0 ? `${selectedIds.size} item${selectedIds.size > 1 ? 's' : ''} selected` : 'Select items to add'}
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
style={{
|
|
||||||
padding: '10px 16px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: `1px solid ${colors.cardBorder}`,
|
|
||||||
backgroundColor: colors.btnSecondaryBg,
|
|
||||||
color: colors.btnSecondaryText,
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleAddToCanvas}
|
|
||||||
disabled={selectedIds.size === 0}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
padding: '10px 16px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: selectedIds.size > 0 ? colors.btnPrimaryBg : colors.btnSecondaryBg,
|
|
||||||
color: selectedIds.size > 0 ? colors.btnPrimaryText : colors.textMuted,
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
cursor: selectedIds.size > 0 ? 'pointer' : 'not-allowed',
|
|
||||||
opacity: selectedIds.size > 0 ? 1 : 0.6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>🔒</span>
|
|
||||||
Add to Private Workspace
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Privacy note */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: '12px 20px',
|
|
||||||
backgroundColor: colors.cardBg,
|
|
||||||
borderTop: `1px solid ${colors.cardBorder}`,
|
|
||||||
borderRadius: '0 0 12px 12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: '11px',
|
|
||||||
color: colors.textMuted,
|
|
||||||
textAlign: 'center',
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
🔒 Private = Only you can see (encrypted in browser) • Drag outside Private Workspace to share
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -4,8 +4,7 @@ import { useDialogs } from "tldraw"
|
||||||
import { SettingsDialog } from "./SettingsDialog"
|
import { SettingsDialog } from "./SettingsDialog"
|
||||||
import { getFathomApiKey, saveFathomApiKey, removeFathomApiKey, isFathomApiKeyConfigured } from "../lib/fathomApiKey"
|
import { getFathomApiKey, saveFathomApiKey, removeFathomApiKey, isFathomApiKeyConfigured } from "../lib/fathomApiKey"
|
||||||
import { linkEmailToAccount, checkEmailStatus, type LookupResult } from "../lib/auth/cryptidEmailService"
|
import { linkEmailToAccount, checkEmailStatus, type LookupResult } from "../lib/auth/cryptidEmailService"
|
||||||
import { GoogleDataService, type GoogleService, type ShareableItem } from "../lib/google"
|
import { GoogleDataService, type GoogleService } from "../lib/google"
|
||||||
import { GoogleExportBrowser } from "../components/GoogleExportBrowser"
|
|
||||||
|
|
||||||
// AI tool model configurations
|
// AI tool model configurations
|
||||||
const AI_TOOLS = [
|
const AI_TOOLS = [
|
||||||
|
|
@ -155,7 +154,7 @@ export function UserSettingsModal({ onClose, isDarkMode, onToggleDarkMode }: Use
|
||||||
photos: 0,
|
photos: 0,
|
||||||
calendar: 0,
|
calendar: 0,
|
||||||
})
|
})
|
||||||
const [showGoogleExportBrowser, setShowGoogleExportBrowser] = useState(false)
|
const [showGoogleDataBrowser, setShowGoogleDataBrowser] = useState(false)
|
||||||
|
|
||||||
// Check API key status
|
// Check API key status
|
||||||
const checkApiKeys = () => {
|
const checkApiKeys = () => {
|
||||||
|
|
@ -305,17 +304,6 @@ export function UserSettingsModal({ onClose, isDarkMode, onToggleDarkMode }: Use
|
||||||
// Calculate total imported items
|
// Calculate total imported items
|
||||||
const totalGoogleItems = Object.values(googleCounts).reduce((a, b) => a + b, 0)
|
const totalGoogleItems = Object.values(googleCounts).reduce((a, b) => a + b, 0)
|
||||||
|
|
||||||
// Handle adding items to canvas from Google Data Browser
|
|
||||||
const handleAddToCanvas = async (items: ShareableItem[], position: { x: number; y: number }) => {
|
|
||||||
// For now, emit a custom event that Board.tsx can listen to
|
|
||||||
// In Phase 3, this will add items to the Private Workspace zone
|
|
||||||
window.dispatchEvent(new CustomEvent('add-google-items-to-canvas', {
|
|
||||||
detail: { items, position }
|
|
||||||
}));
|
|
||||||
setShowGoogleExportBrowser(false);
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle escape key and click outside
|
// Handle escape key and click outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
|
|
@ -946,7 +934,7 @@ export function UserSettingsModal({ onClose, isDarkMode, onToggleDarkMode }: Use
|
||||||
<button
|
<button
|
||||||
className="settings-action-btn"
|
className="settings-action-btn"
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
onClick={() => setShowGoogleExportBrowser(true)}
|
onClick={() => setShowGoogleDataBrowser(true)}
|
||||||
disabled={totalGoogleItems === 0}
|
disabled={totalGoogleItems === 0}
|
||||||
>
|
>
|
||||||
Open Data Browser
|
Open Data Browser
|
||||||
|
|
@ -987,14 +975,6 @@ export function UserSettingsModal({ onClose, isDarkMode, onToggleDarkMode }: Use
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Google Export Browser Modal */}
|
|
||||||
<GoogleExportBrowser
|
|
||||||
isOpen={showGoogleExportBrowser}
|
|
||||||
onClose={() => setShowGoogleExportBrowser(false)}
|
|
||||||
onAddToCanvas={handleAddToCanvas}
|
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue