Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
import { useState, useEffect } from 'react';
import { X, ChevronUp, ChevronDown, Settings, Plus, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
type TickerPosition = 'top' | 'bottom' | 'hidden';
const TICKER_SETTINGS_KEY = 'cyber-ticker-settings';
const CUSTOM_HEADLINES_KEY = 'cyber-ticker-custom-headlines';
interface TickerSettings {
position: TickerPosition;
opacity: number;
}
const defaultSettings: TickerSettings = {
position: 'top',
opacity: 60,
};
const defaultHeadlines = [
"SYSTEM AKTIV",
"ALLE SYSTEMER ONLINE",
"SIKKERHEDSNIVEAU: MAKSIMUM",
"DATASTREAM STABIL",
"NEURAL NETVÆRK OPERATIONELT",
"KRYPTERING: AES-256",
"FORBINDELSE SIKRET",
"PROTOKOL INITIERET",
];
const BreakingTicker = () => {
const [settings, setSettings] = useState<TickerSettings>(defaultSettings);
const [customHeadlines, setCustomHeadlines] = useState<string[]>([]);
const [newHeadline, setNewHeadline] = useState('');
useEffect(() => {
const savedSettings = localStorage.getItem(TICKER_SETTINGS_KEY);
if (savedSettings) {
try {
setSettings(JSON.parse(savedSettings));
} catch {}
}
const savedHeadlines = localStorage.getItem(CUSTOM_HEADLINES_KEY);
if (savedHeadlines) {
try {
setCustomHeadlines(JSON.parse(savedHeadlines));
} catch {}
}
}, []);
const updateSettings = (updates: Partial<TickerSettings>) => {
const newSettings = { ...settings, ...updates };
setSettings(newSettings);
localStorage.setItem(TICKER_SETTINGS_KEY, JSON.stringify(newSettings));
};
const addHeadline = () => {
if (newHeadline.trim()) {
const updated = [...customHeadlines, newHeadline.trim().toUpperCase()];
setCustomHeadlines(updated);
localStorage.setItem(CUSTOM_HEADLINES_KEY, JSON.stringify(updated));
setNewHeadline('');
}
};
const removeHeadline = (index: number) => {
const updated = customHeadlines.filter((_, i) => i !== index);
setCustomHeadlines(updated);
localStorage.setItem(CUSTOM_HEADLINES_KEY, JSON.stringify(updated));
};
const allHeadlines = [...customHeadlines, ...defaultHeadlines];
const repeatedHeadlines = [...allHeadlines, ...allHeadlines, ...allHeadlines];
if (settings.position === 'hidden') {
return (
<Button
variant="ghost"
size="sm"
className="fixed bottom-4 right-4 z-50 bg-destructive/20 hover:bg-destructive/40 text-destructive"
onClick={() => updateSettings({ position: 'top' })}
>
Vis ticker
</Button>
);
}
const positionClasses = settings.position === 'top'
? 'top-0'
: 'bottom-0';
return (
<div
className={cn(
"fixed left-0 right-0 z-40 backdrop-blur-sm border-destructive/50 pointer-events-auto",
positionClasses,
settings.position === 'top' ? 'border-b' : 'border-t'
)}
style={{
backgroundColor: `hsl(var(--destructive) / ${settings.opacity / 100})`,
}}
>
<div className="flex items-center">
<div
className="px-3 py-1.5 font-display text-xs font-bold text-destructive uppercase tracking-wider shrink-0 flex items-center gap-2"
style={{ backgroundColor: `hsl(var(--background) / 0.9)` }}
>
<span className="inline-block animate-pulse text-destructive"></span>
<span className="text-destructive">BREAKING</span>
</div>
<div className="ticker flex-1 py-1.5 overflow-hidden">
<div className="ticker-content font-mono text-xs text-destructive-foreground/90">
{repeatedHeadlines.map((headline, i) => (
<span key={i} className="mx-6">
{headline}
<span className="mx-3 text-destructive-foreground/40">///</span>
</span>
))}
</div>
</div>
<div className="flex items-center gap-1 px-2 shrink-0">
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-destructive-foreground/70 hover:text-destructive-foreground hover:bg-destructive/30"
>
<Settings className="w-3 h-3" />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-72 p-3 bg-card border-border">
<div className="space-y-4">
<div className="space-y-2">
<div className="text-xs font-medium text-muted-foreground">Gennemsigtighed</div>
<input
type="range"
min="20"
max="100"
value={settings.opacity}
onChange={(e) => updateSettings({ opacity: Number(e.target.value) })}
className="w-full h-2 bg-secondary rounded-lg appearance-none cursor-pointer"
/>
<div className="text-xs text-muted-foreground text-right">{settings.opacity}%</div>
</div>
<div className="border-t border-border pt-3 space-y-2">
<div className="text-xs font-medium text-muted-foreground">Custom Headlines</div>
<div className="flex gap-2">
<Input
value={newHeadline}
onChange={(e) => setNewHeadline(e.target.value)}
placeholder="Tilføj headline..."
className="h-7 text-xs"
onKeyDown={(e) => e.key === 'Enter' && addHeadline()}
/>
<Button size="sm" className="h-7 px-2" onClick={addHeadline}>
<Plus className="w-3 h-3" />
</Button>
</div>
{customHeadlines.length > 0 && (
<div className="space-y-1 max-h-32 overflow-y-auto">
{customHeadlines.map((headline, i) => (
<div key={i} className="flex items-center justify-between gap-2 text-xs bg-secondary/50 rounded px-2 py-1">
<span className="truncate font-mono">{headline}</span>
<Button
variant="ghost"
size="sm"
className="h-5 w-5 p-0 text-destructive hover:text-destructive"
onClick={() => removeHeadline(i)}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
))}
</div>
)}
</div>
</div>
</PopoverContent>
</Popover>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-destructive-foreground/70 hover:text-destructive-foreground hover:bg-destructive/30"
onClick={() => updateSettings({ position: settings.position === 'top' ? 'bottom' : 'top' })}
title={settings.position === 'top' ? 'Flyt til bund' : 'Flyt til top'}
>
{settings.position === 'top' ? <ChevronDown className="w-3 h-3" /> : <ChevronUp className="w-3 h-3" />}
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 text-destructive-foreground/70 hover:text-destructive-foreground hover:bg-destructive/30"
onClick={() => updateSettings({ position: 'hidden' })}
title="Skjul ticker"
>
<X className="w-3 h-3" />
</Button>
</div>
</div>
</div>
);
};
export default BreakingTicker;