mirror of
https://github.com/dergigi/boris.git
synced 2026-02-16 12:34:41 +01:00
Updated handleLinkClick in PWASettings to check if URL is an internal route (starts with /) and navigate directly, otherwise wrap external URLs with /r/ path. This fixes the third relay education link to open the nostr article correctly.
227 lines
8.6 KiB
TypeScript
227 lines
8.6 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { useNavigate } from 'react-router-dom'
|
||
import { faDownload, faCheckCircle, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||
import { usePWAInstall } from '../../hooks/usePWAInstall'
|
||
import { useIsMobile } from '../../hooks/useMediaQuery'
|
||
import { UserSettings } from '../../services/settingsService'
|
||
import { getImageCacheStatsAsync, clearImageCache } from '../../services/imageCacheService'
|
||
|
||
interface PWASettingsProps {
|
||
settings: UserSettings
|
||
onUpdate: (updates: Partial<UserSettings>) => void
|
||
onClose?: () => void
|
||
}
|
||
|
||
const PWASettings: React.FC<PWASettingsProps> = ({ settings, onUpdate, onClose }) => {
|
||
const navigate = useNavigate()
|
||
const isMobile = useIsMobile()
|
||
const { isInstallable, isInstalled, installApp } = usePWAInstall()
|
||
const [cacheStats, setCacheStats] = useState<{
|
||
totalSizeMB: number
|
||
itemCount: number
|
||
items: Array<{ url: string, sizeMB: number }>
|
||
}>({ totalSizeMB: 0, itemCount: 0, items: [] })
|
||
|
||
const handleInstall = async () => {
|
||
if (isInstalled) return
|
||
const success = await installApp()
|
||
if (success) {
|
||
// Installation successful
|
||
}
|
||
}
|
||
|
||
const handleLinkClick = (url: string) => {
|
||
if (onClose) onClose()
|
||
// If it's an internal route (starts with /), navigate directly
|
||
if (url.startsWith('/')) {
|
||
navigate(url)
|
||
} else {
|
||
// External URL: wrap with /r/ path
|
||
navigate(`/r/${encodeURIComponent(url)}`)
|
||
}
|
||
}
|
||
|
||
const handleClearCache = async () => {
|
||
if (confirm('Are you sure you want to clear all cached images?')) {
|
||
await clearImageCache()
|
||
const stats = await getImageCacheStatsAsync()
|
||
setCacheStats(stats)
|
||
}
|
||
}
|
||
|
||
// Update cache stats periodically
|
||
useEffect(() => {
|
||
const updateStats = async () => {
|
||
const stats = await getImageCacheStatsAsync()
|
||
setCacheStats(stats)
|
||
}
|
||
|
||
updateStats() // Initial load
|
||
const interval = setInterval(updateStats, 3000) // Update every 3 seconds
|
||
return () => clearInterval(interval)
|
||
}, [])
|
||
|
||
return (
|
||
<div className="settings-section">
|
||
<h3 className="section-title">App & Airplane Mode</h3>
|
||
|
||
<div style={{ display: 'flex', gap: '2rem', alignItems: 'stretch' }}>
|
||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
|
||
<p className="setting-description" style={{ marginBottom: '1rem', color: 'var(--color-text-secondary)', fontSize: '0.875rem' }}>
|
||
Boris is offline‑first by design. You can read, create highlights, and browse your library without being connected to the internet. Boris will store changes locally and sync later.
|
||
</p>
|
||
|
||
{/* Flight Mode Section - Checkboxes First */}
|
||
<div className="setting-group" style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
|
||
<label htmlFor="enableImageCache" className="checkbox-label" style={{ marginBottom: 0 }}>
|
||
<input
|
||
id="enableImageCache"
|
||
type="checkbox"
|
||
checked={settings.enableImageCache ?? true}
|
||
onChange={(e) => onUpdate({ enableImageCache: e.target.checked })}
|
||
className="setting-checkbox"
|
||
/>
|
||
<span>Use local image cache</span>
|
||
</label>
|
||
|
||
{(settings.enableImageCache ?? true) && (
|
||
<div style={{
|
||
fontSize: '0.85rem',
|
||
color: 'var(--text-secondary)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '0.5rem'
|
||
}}>
|
||
<span style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
|
||
( {cacheStats.totalSizeMB.toFixed(1)} MB /
|
||
<input
|
||
id="imageCacheSizeMB"
|
||
type="number"
|
||
min="10"
|
||
max="500"
|
||
value={settings.imageCacheSizeMB ?? 210}
|
||
onChange={(e) => onUpdate({ imageCacheSizeMB: parseInt(e.target.value) || 210 })}
|
||
style={{
|
||
width: '50px',
|
||
padding: '0.15rem 0.35rem',
|
||
background: 'var(--surface-secondary)',
|
||
border: '1px solid var(--border-color, #333)',
|
||
borderRadius: '4px',
|
||
color: 'inherit',
|
||
fontSize: 'inherit',
|
||
fontFamily: 'inherit',
|
||
textAlign: 'center'
|
||
}}
|
||
/>
|
||
MB used )
|
||
</span>
|
||
<FontAwesomeIcon
|
||
icon={faTrash}
|
||
onClick={handleClearCache}
|
||
title="Clear cache"
|
||
style={{ cursor: 'pointer', fontSize: '0.85rem', opacity: 0.7 }}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* PWA Install Section - Paragraphs */}
|
||
<div className="setting-group">
|
||
<p className="setting-description" style={{ marginTop: '0.5rem', marginBottom: '0.75rem', color: 'var(--color-text-secondary)', fontSize: '0.875rem' }}>
|
||
<strong>Note:</strong> Boris works best with a local relay. Consider running{' '}
|
||
<a
|
||
href="https://github.com/greenart7c3/Citrine?tab=readme-ov-file#download"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{ color: 'var(--accent, #8b5cf6)' }}
|
||
>
|
||
Citrine
|
||
</a>
|
||
{' or '}
|
||
<a
|
||
href="https://github.com/CodyTseng/nostr-relay-tray/releases"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
style={{ color: 'var(--accent, #8b5cf6)' }}
|
||
>
|
||
nostr-relay-tray
|
||
</a>
|
||
{' '}to bring full offline functionality to Boris. Don't know what relays are? Learn more{' '}
|
||
<a
|
||
onClick={(e) => {
|
||
e.preventDefault()
|
||
handleLinkClick('https://nostr.how/en/relays')
|
||
}}
|
||
style={{ color: 'var(--accent, #8b5cf6)', cursor: 'pointer' }}
|
||
>
|
||
here
|
||
</a>
|
||
{', '}
|
||
<a
|
||
onClick={(e) => {
|
||
e.preventDefault()
|
||
handleLinkClick('https://davidebtc186.substack.com/p/the-importance-of-hosting-your-own')
|
||
}}
|
||
style={{ color: 'var(--accent, #8b5cf6)', cursor: 'pointer' }}
|
||
>
|
||
here
|
||
</a>
|
||
{', and '}
|
||
<a
|
||
onClick={(e) => {
|
||
e.preventDefault()
|
||
handleLinkClick('/a/naddr1qvzqqqr4gupzq3svyhng9ld8sv44950j957j9vchdktj7cxumsep9mvvjthc2pjuqq9hyetvv9uj6um9w36hq9mgjg8')
|
||
}}
|
||
style={{ color: 'var(--accent, #8b5cf6)', cursor: 'pointer' }}
|
||
>
|
||
here
|
||
</a>
|
||
.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="setting-group">
|
||
<label htmlFor="useLocalRelayAsCache" className="checkbox-label">
|
||
<input
|
||
id="useLocalRelayAsCache"
|
||
type="checkbox"
|
||
checked={settings.useLocalRelayAsCache ?? true}
|
||
onChange={(e) => onUpdate({ useLocalRelayAsCache: e.target.checked })}
|
||
className="setting-checkbox"
|
||
/>
|
||
<span>Use local relays as cache</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div className="setting-group">
|
||
<p className="setting-description" style={{ marginBottom: '1rem', color: 'var(--color-text-secondary)', fontSize: '0.875rem' }}>
|
||
Install Boris on your device for a native app experience.
|
||
</p>
|
||
<button
|
||
onClick={handleInstall}
|
||
className="zap-preset-btn"
|
||
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}
|
||
disabled={isInstalled || !isInstallable}
|
||
>
|
||
<FontAwesomeIcon icon={isInstalled ? faCheckCircle : faDownload} />
|
||
{isInstalled ? 'Installed' : 'Install App'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{!isMobile && (
|
||
<img
|
||
src="/pwa.svg"
|
||
alt="Progressive Web App"
|
||
style={{ width: '30%', height: 'auto', flexShrink: 0, opacity: 0.8 }}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default PWASettings
|
||
|