Files
boris/src/components/Settings/PWASettings.tsx
Gigi 033ef5e995 fix: relay article link now opens via /a/ path instead of /r/
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.
2025-10-23 00:54:29 +02:00

227 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 offlinefirst 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