Port UI changes from sample-front-end: toggle switches, Eye icon, Link icon
Co-authored-by: Koha9 <36852125+Koha9@users.noreply.github.com>
This commit is contained in:
@@ -16,10 +16,10 @@ import {
|
|||||||
Calendar,
|
Calendar,
|
||||||
Clock,
|
Clock,
|
||||||
Repeat,
|
Repeat,
|
||||||
CheckSquare,
|
|
||||||
Square,
|
|
||||||
Type,
|
Type,
|
||||||
Code2
|
Code2,
|
||||||
|
Link,
|
||||||
|
Eye
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
// Generate a UUID for mapping rules
|
// Generate a UUID for mapping rules
|
||||||
@@ -104,17 +104,12 @@ const MAPPING_THEME = {
|
|||||||
const deriveEffectiveSchedule = (schedule: ScheduleSettings, tab: ScheduleMode): ScheduleSettings => {
|
const deriveEffectiveSchedule = (schedule: ScheduleSettings, tab: ScheduleMode): ScheduleSettings => {
|
||||||
const derived = { ...schedule };
|
const derived = { ...schedule };
|
||||||
|
|
||||||
if (tab === ScheduleMode.CRON) {
|
// Unified logic: If the mode matches the tab, we keep it (Enabled).
|
||||||
derived.mode = derived.cronExpression.trim() !== '' ? ScheduleMode.CRON : ScheduleMode.DISABLED;
|
// If the mode doesn't match (e.g. it was DISABLED), then in the context of this tab, it remains Disabled until the user toggles the switch.
|
||||||
|
if (derived.mode === tab) {
|
||||||
|
derived.mode = tab;
|
||||||
} else {
|
} else {
|
||||||
// For Daily/Weekly
|
derived.mode = ScheduleMode.DISABLED;
|
||||||
// If the mode matches the tab, we keep it (Enabled).
|
|
||||||
// If the mode doesn't match (e.g. it was CRON or DISABLED), then in the context of this tab, it is effectively Disabled until the user checks the box.
|
|
||||||
if (derived.mode === tab) {
|
|
||||||
derived.mode = tab;
|
|
||||||
} else {
|
|
||||||
derived.mode = ScheduleMode.DISABLED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return derived;
|
return derived;
|
||||||
};
|
};
|
||||||
@@ -200,7 +195,7 @@ const MappingGroupEditor: React.FC<MappingGroupEditorProps> = ({
|
|||||||
onChange={(e) => handleUpdate(rule.id, 'search', e.target.value)}
|
onChange={(e) => handleUpdate(rule.id, 'search', e.target.value)}
|
||||||
className={`flex-1 min-w-0 border rounded px-1.5 py-1 text-xs focus:outline-none transition-colors ${leftInputClass || defaultInputStyle}`}
|
className={`flex-1 min-w-0 border rounded px-1.5 py-1 text-xs focus:outline-none transition-colors ${leftInputClass || defaultInputStyle}`}
|
||||||
/>
|
/>
|
||||||
<ArrowRightCircle size={10} className="text-gray-600 flex-none opacity-50" />
|
<Link size={12} className="text-gray-600 flex-none opacity-50" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={rightPlaceholder}
|
placeholder={rightPlaceholder}
|
||||||
@@ -634,35 +629,49 @@ const StrategySelector: React.FC<StrategySelectorProps> = ({
|
|||||||
{/* Tab Content */}
|
{/* Tab Content */}
|
||||||
<div className="mb-4 min-h-[50px]">
|
<div className="mb-4 min-h-[50px]">
|
||||||
{activeScheduleTab === ScheduleMode.CRON && (
|
{activeScheduleTab === ScheduleMode.CRON && (
|
||||||
<div className="space-y-2 animate-in fade-in duration-200">
|
<div className="flex flex-col animate-in fade-in duration-200">
|
||||||
<div className="flex items-center space-x-2">
|
{/* Top Row: Label + Switch */}
|
||||||
<span className="text-gray-500 font-mono text-xs">Cron:</span>
|
<div className="flex items-center justify-between mb-3 px-1">
|
||||||
<input
|
<span className="text-xs text-gray-400 font-medium">Enable Cron Schedule</span>
|
||||||
type="text"
|
<button
|
||||||
value={localSchedule.cronExpression}
|
onClick={() => toggleScheduleEnable(ScheduleMode.CRON)}
|
||||||
onChange={(e) => handleUpdateSchedule('cronExpression', e.target.value)}
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-plex-orange focus:ring-offset-2 focus:ring-offset-gray-900 ${localSchedule.mode === ScheduleMode.CRON ? 'bg-plex-orange' : 'bg-gray-700'}`}
|
||||||
placeholder="0 0 * * *"
|
>
|
||||||
className="flex-1 bg-gray-800 border border-gray-700 rounded-md px-2.5 py-1.5 text-xs text-gray-200 font-mono focus:border-plex-orange focus:outline-none focus:ring-1 focus:ring-plex-orange placeholder-gray-600"
|
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSchedule.mode === ScheduleMode.CRON ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||||
/>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className={`space-y-2 transition-opacity duration-200 ${localSchedule.mode !== ScheduleMode.CRON ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-gray-500 font-mono text-xs">Cron:</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={localSchedule.cronExpression}
|
||||||
|
onChange={(e) => handleUpdateSchedule('cronExpression', e.target.value)}
|
||||||
|
placeholder="0 0 * * *"
|
||||||
|
disabled={localSchedule.mode !== ScheduleMode.CRON}
|
||||||
|
className="flex-1 bg-gray-800 border border-gray-700 rounded-md px-2.5 py-1.5 text-xs text-gray-200 font-mono focus:border-plex-orange focus:outline-none focus:ring-1 focus:ring-plex-orange placeholder-gray-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-gray-500">
|
||||||
|
Unix-cron format.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-gray-500">
|
|
||||||
Unix-cron format. Leave empty to disable schedule.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeScheduleTab === ScheduleMode.DAILY && (
|
{activeScheduleTab === ScheduleMode.DAILY && (
|
||||||
<div className="flex flex-col animate-in fade-in duration-200">
|
<div className="flex flex-col animate-in fade-in duration-200">
|
||||||
{/* Top Row: Checkbox + Label */}
|
{/* Top Row: Label + Switch */}
|
||||||
<div className="flex items-center justify-start space-x-2 mb-2">
|
<div className="flex items-center justify-between mb-3 px-1">
|
||||||
|
<span className="text-xs text-gray-400 font-medium">Enable Daily Run</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleScheduleEnable(ScheduleMode.DAILY)}
|
onClick={() => toggleScheduleEnable(ScheduleMode.DAILY)}
|
||||||
className={`transition-colors flex-none ${localSchedule.mode === ScheduleMode.DAILY ? 'text-plex-orange' : 'text-gray-500 hover:text-gray-400'}`}
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-plex-orange focus:ring-offset-2 focus:ring-offset-gray-900 ${localSchedule.mode === ScheduleMode.DAILY ? 'bg-plex-orange' : 'bg-gray-700'}`}
|
||||||
title={localSchedule.mode === ScheduleMode.DAILY ? "Schedule Enabled" : "Schedule Disabled"}
|
|
||||||
>
|
>
|
||||||
{localSchedule.mode === ScheduleMode.DAILY ? <CheckSquare size={16} /> : <Square size={16} />}
|
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSchedule.mode === ScheduleMode.DAILY ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||||
</button>
|
</button>
|
||||||
<label className="text-xs text-gray-400 font-medium">Run daily at:</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Row: Centered Native Time Input */}
|
{/* Bottom Row: Centered Native Time Input */}
|
||||||
@@ -680,16 +689,15 @@ const StrategySelector: React.FC<StrategySelectorProps> = ({
|
|||||||
|
|
||||||
{activeScheduleTab === ScheduleMode.WEEKLY && (
|
{activeScheduleTab === ScheduleMode.WEEKLY && (
|
||||||
<div className="flex flex-col animate-in fade-in duration-200">
|
<div className="flex flex-col animate-in fade-in duration-200">
|
||||||
{/* Top Row: Checkbox + Label */}
|
{/* Top Row: Label + Switch */}
|
||||||
<div className="flex items-center justify-start space-x-2 mb-2">
|
<div className="flex items-center justify-between mb-3 px-1">
|
||||||
<button
|
<span className="text-xs text-gray-400 font-medium">Enable Weekly Run</span>
|
||||||
|
<button
|
||||||
onClick={() => toggleScheduleEnable(ScheduleMode.WEEKLY)}
|
onClick={() => toggleScheduleEnable(ScheduleMode.WEEKLY)}
|
||||||
className={`transition-colors flex-none ${localSchedule.mode === ScheduleMode.WEEKLY ? 'text-plex-orange' : 'text-gray-500 hover:text-gray-400'}`}
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-plex-orange focus:ring-offset-2 focus:ring-offset-gray-900 ${localSchedule.mode === ScheduleMode.WEEKLY ? 'bg-plex-orange' : 'bg-gray-700'}`}
|
||||||
title={localSchedule.mode === ScheduleMode.WEEKLY ? "Schedule Enabled" : "Schedule Disabled"}
|
>
|
||||||
>
|
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSchedule.mode === ScheduleMode.WEEKLY ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||||
{localSchedule.mode === ScheduleMode.WEEKLY ? <CheckSquare size={16} /> : <Square size={16} />}
|
</button>
|
||||||
</button>
|
|
||||||
<label className="text-xs text-gray-400 font-medium">Run on days:</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Middle Row: Full Width Capsules */}
|
{/* Middle Row: Full Width Capsules */}
|
||||||
@@ -728,20 +736,22 @@ const StrategySelector: React.FC<StrategySelectorProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auto Watch Checkbox */}
|
{/* Auto Watch Switch */}
|
||||||
<div className="flex items-center mb-4 px-1">
|
<div className="flex items-center justify-between mb-4 mt-2 px-1">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="p-1.5 rounded-lg bg-orange-500/10 border border-orange-500/20 text-orange-400">
|
||||||
|
<Eye size={16} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-medium text-gray-200">Watch Local Changes</span>
|
||||||
|
<span className="text-[10px] text-gray-500">Auto-sync when local playlist updates</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleUpdateSchedule('autoWatch', !localSchedule.autoWatch)}
|
onClick={() => handleUpdateSchedule('autoWatch', !localSchedule.autoWatch)}
|
||||||
className="flex items-center space-x-2 group"
|
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-plex-orange focus:ring-offset-2 focus:ring-offset-gray-900 ${localSchedule.autoWatch ? 'bg-plex-orange' : 'bg-gray-700'}`}
|
||||||
>
|
>
|
||||||
{localSchedule.autoWatch ? (
|
<span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${localSchedule.autoWatch ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||||
<CheckSquare size={16} className="text-plex-orange" />
|
|
||||||
) : (
|
|
||||||
<Square size={16} className="text-gray-600 group-hover:text-gray-400" />
|
|
||||||
)}
|
|
||||||
<span className={`text-xs ${localSchedule.autoWatch ? 'text-gray-200' : 'text-gray-500 group-hover:text-gray-400'}`}>
|
|
||||||
Watch for local playlist changes
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ export interface PathMappingConfig {
|
|||||||
regex: PathMappingRules;
|
regex: PathMappingRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BackupSettings {
|
||||||
|
enabled: boolean;
|
||||||
|
retentionCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RegexReplacement {
|
export interface RegexReplacement {
|
||||||
id: string;
|
id: string;
|
||||||
pattern: string;
|
pattern: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user