Rename Kyoo to Omnyx & add page settings
Rename project branding from "Kyoo" to "Omnyx" across README, index.html, metadata.json, typedoc and various UI components. Add support for page-level settings: pageTitle, favicon (Base64 upload/preview), and customColors (color scheme) — introduced CustomColors type, persisted via API types and converters, and wired into updateSettings/fetchSettings flows. UI: SettingsView adds page settings UI (upload, preview, color pickers) and handlers; App applies pageTitle, favicon and sets CSS variables for customColors; Sidebar and Header now display the configured page title. Also update importer modules and docs to use the new project name in logs/comments.
This commit is contained in:
@@ -95,7 +95,7 @@ export default function Header({
|
||||
)} />
|
||||
</div>
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-white to-white/80">
|
||||
kyoo
|
||||
omnyx
|
||||
</span>
|
||||
</Link>
|
||||
<button
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { MediaCategory, UserSettings } from '@/types';
|
||||
import { MediaCategory, UserSettings, CustomColors } from '@/types';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Film, Music, Book, Tv, Gamepad2, ShieldAlert, LayoutGrid, List, Globe, Monitor, Sun, Moon, Save, ArrowLeft } from 'lucide-react';
|
||||
import { Film, Music, Book, Tv, Gamepad2, ShieldAlert, LayoutGrid, List, Globe, Monitor, Sun, Moon, Save, ArrowLeft, Type, Image, Palette } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { fetchSettings, updateSettings } from '@/api';
|
||||
import { useTheme } from '@/contexts/ThemeContext';
|
||||
@@ -46,6 +46,12 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
||||
|
||||
// Page Settings State
|
||||
const [pageTitle, setPageTitle] = useState<string>('');
|
||||
const [favicon, setFavicon] = useState<string>('');
|
||||
const [customColors, setCustomColors] = useState<CustomColors>({});
|
||||
const [faviconPreview, setFaviconPreview] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
@@ -56,6 +62,10 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
||||
const loadedSettings = await fetchSettings();
|
||||
if (loadedSettings) {
|
||||
setSettings(loadedSettings);
|
||||
setPageTitle(loadedSettings.pageTitle || '');
|
||||
setFavicon(loadedSettings.favicon || '');
|
||||
setCustomColors(loadedSettings.customColors || {});
|
||||
setFaviconPreview(loadedSettings.favicon || '');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error);
|
||||
@@ -68,7 +78,13 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
||||
setIsSaving(true);
|
||||
setSaveStatus('idle');
|
||||
try {
|
||||
const savedSettings = await updateSettings(settings);
|
||||
const updatedSettings: UserSettings = {
|
||||
...settings,
|
||||
pageTitle: pageTitle || undefined,
|
||||
favicon: favicon || undefined,
|
||||
customColors: Object.keys(customColors).length > 0 ? customColors : undefined,
|
||||
};
|
||||
const savedSettings = await updateSettings(updatedSettings);
|
||||
if (savedSettings) {
|
||||
setSettings(savedSettings);
|
||||
setSaveStatus('success');
|
||||
@@ -96,6 +112,31 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleFaviconUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const base64 = reader.result as string;
|
||||
setFavicon(base64);
|
||||
setFaviconPreview(base64);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFavicon = () => {
|
||||
setFavicon('');
|
||||
setFaviconPreview('');
|
||||
};
|
||||
|
||||
const handleColorChange = (colorKey: keyof CustomColors, value: string) => {
|
||||
setCustomColors(prev => ({
|
||||
...prev,
|
||||
[colorKey]: value || undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
@@ -342,6 +383,115 @@ export default function SettingsView({ onSettingsSaved }: SettingsViewProps) {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Page Settings */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-black text-foreground mb-6">Page Settings</h2>
|
||||
<div className="bg-muted/50 backdrop-blur-sm rounded-2xl p-6 border border-border/50 space-y-6">
|
||||
{/* Page Title */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Type size={18} className="text-[#6d28d9]" />
|
||||
<Label className="text-sm font-black text-foreground">Custom Page Title</Label>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={pageTitle}
|
||||
onChange={(e) => setPageTitle(e.target.value)}
|
||||
placeholder="Leave empty for default title"
|
||||
className="w-full px-4 py-3 rounded-xl bg-background border border-border/50 text-foreground placeholder:text-muted-foreground/50 focus:border-[#6d28d9] focus:outline-none transition-all"
|
||||
/>
|
||||
<p className="text-xs font-medium text-muted-foreground mt-2">
|
||||
Custom title for your page. Leave empty to use the default title.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Favicon Upload */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Image size={18} className="text-[#6d28d9]" />
|
||||
<Label className="text-sm font-black text-foreground">Favicon / Icon</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{faviconPreview && (
|
||||
<div className="relative">
|
||||
<img
|
||||
src={faviconPreview}
|
||||
alt="Favicon preview"
|
||||
className="w-16 h-16 rounded-xl object-cover border border-border/50"
|
||||
/>
|
||||
<button
|
||||
onClick={handleRemoveFavicon}
|
||||
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs hover:bg-red-600 transition-colors"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleFaviconUpload}
|
||||
className="hidden"
|
||||
id="favicon-upload"
|
||||
/>
|
||||
<label
|
||||
htmlFor="favicon-upload"
|
||||
className="inline-flex items-center gap-2 px-4 py-3 rounded-xl bg-background border border-border/50 text-foreground hover:bg-muted hover:border-[#6d28d9]/30 cursor-pointer transition-all"
|
||||
>
|
||||
<Image size={16} />
|
||||
{favicon ? 'Change favicon' : 'Upload favicon'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs font-medium text-muted-foreground mt-2">
|
||||
Upload a custom favicon or icon. The image will be converted to Base64 format.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Custom Colors */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Palette size={18} className="text-[#6d28d9]" />
|
||||
<Label className="text-sm font-black text-foreground">Custom Colors</Label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{[
|
||||
{ key: 'primary', label: 'Primary Color' },
|
||||
{ key: 'secondary', label: 'Secondary Color' },
|
||||
{ key: 'background', label: 'Background Color' },
|
||||
{ key: 'surface', label: 'Surface Color' },
|
||||
{ key: 'text', label: 'Text Color' },
|
||||
{ key: 'muted', label: 'Muted Text Color' },
|
||||
{ key: 'border', label: 'Border Color' },
|
||||
].map(({ key, label }) => (
|
||||
<div key={key} className="flex items-center gap-3 p-3 rounded-xl bg-background border border-border/50">
|
||||
<input
|
||||
type="color"
|
||||
value={customColors[key as keyof CustomColors] || '#6d28d9'}
|
||||
onChange={(e) => handleColorChange(key as keyof CustomColors, e.target.value)}
|
||||
className="w-10 h-10 rounded-lg cursor-pointer border-0"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<Label className="text-xs font-black text-foreground">{label}</Label>
|
||||
<input
|
||||
type="text"
|
||||
value={customColors[key as keyof CustomColors] || ''}
|
||||
onChange={(e) => handleColorChange(key as keyof CustomColors, e.target.value)}
|
||||
placeholder="#6d28d9"
|
||||
className="w-full mt-1 px-2 py-1 rounded-lg bg-muted border border-border/30 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-[#6d28d9] focus:outline-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs font-medium text-muted-foreground mt-2">
|
||||
Leave color fields empty to use the default theme colors.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,9 +31,10 @@ import { CATEGORY_PATHS } from '@/constants';
|
||||
interface SidebarProps {
|
||||
enabledCategories: MediaCategory[];
|
||||
onToggleCategory: (category: MediaCategory) => void;
|
||||
pageTitle?: string;
|
||||
}
|
||||
|
||||
export default function Sidebar({ enabledCategories, onToggleCategory }: SidebarProps) {
|
||||
export default function Sidebar({ enabledCategories, onToggleCategory, pageTitle }: SidebarProps) {
|
||||
const [isMediaExpanded, setIsMediaExpanded] = useState(true);
|
||||
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
@@ -120,7 +121,7 @@ export default function Sidebar({ enabledCategories, onToggleCategory }: Sidebar
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-[#6d28d9] to-[#8b5cf6] rounded-xl flex items-center justify-center shadow-lg shadow-[#6d28d9]/30">
|
||||
<div className="w-5 h-5 rounded-full bg-white" />
|
||||
</div>
|
||||
<span className="text-xl font-black text-foreground">kyoo</span>
|
||||
<span className="text-xl font-black text-foreground">{pageTitle || 'omnyx'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user