fix: phase 9+10 refactoring
This commit is contained in:
@@ -13,6 +13,19 @@ export interface CurrentValues {
|
||||
caption: string;
|
||||
}
|
||||
|
||||
type SuggestionFieldKey = 'title' | 'alt' | 'caption';
|
||||
|
||||
interface SuggestionFieldConfig {
|
||||
key: SuggestionFieldKey;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const SUGGESTION_FIELDS: SuggestionFieldConfig[] = [
|
||||
{ key: 'title', label: 'Title' },
|
||||
{ key: 'alt', label: 'Alt Text' },
|
||||
{ key: 'caption', label: 'Caption' },
|
||||
];
|
||||
|
||||
interface AISuggestionsModalProps {
|
||||
isOpen: boolean;
|
||||
isLoading: boolean;
|
||||
@@ -65,6 +78,51 @@ export const AISuggestionsModal: React.FC<AISuggestionsModalProps> = ({
|
||||
const hasAnySuggestion = suggestions && (suggestions.title || suggestions.alt || suggestions.caption);
|
||||
const hasAnySelected = useTitle || useAlt || useCaption;
|
||||
|
||||
const fieldSelection: Record<SuggestionFieldKey, [boolean, (checked: boolean) => void]> = {
|
||||
title: [useTitle, setUseTitle],
|
||||
alt: [useAlt, setUseAlt],
|
||||
caption: [useCaption, setUseCaption],
|
||||
};
|
||||
|
||||
const renderSuggestionField = (field: SuggestionFieldConfig) => {
|
||||
if (!suggestions?.[field.key]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [isChecked, setChecked] = fieldSelection[field.key];
|
||||
const currentValue = currentValues[field.key];
|
||||
const suggestedValue = suggestions[field.key];
|
||||
|
||||
return (
|
||||
<div key={field.key} className="ai-suggestion-item">
|
||||
<label className="ai-suggestion-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isChecked}
|
||||
onChange={(e) => setChecked(e.target.checked)}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
</label>
|
||||
<div className="ai-suggestion-content">
|
||||
<div className="ai-suggestion-label">
|
||||
{field.label}
|
||||
{currentValue && (
|
||||
<span className="ai-suggestion-has-value" title="This field already has a value">
|
||||
(has existing value)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ai-suggestion-value">{suggestedValue}</div>
|
||||
{currentValue && (
|
||||
<div className="ai-suggestion-current">
|
||||
Current: <em>{currentValue}</em>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ai-suggestions-modal-backdrop" onClick={handleBackdropClick}>
|
||||
<div className="ai-suggestions-modal">
|
||||
@@ -97,93 +155,7 @@ export const AISuggestionsModal: React.FC<AISuggestionsModalProps> = ({
|
||||
<p className="ai-suggestions-intro">
|
||||
Select which AI-generated values to apply. Existing values are preserved by default.
|
||||
</p>
|
||||
|
||||
{suggestions?.title && (
|
||||
<div className="ai-suggestion-item">
|
||||
<label className="ai-suggestion-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useTitle}
|
||||
onChange={(e) => setUseTitle(e.target.checked)}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
</label>
|
||||
<div className="ai-suggestion-content">
|
||||
<div className="ai-suggestion-label">
|
||||
Title
|
||||
{currentValues.title && (
|
||||
<span className="ai-suggestion-has-value" title="This field already has a value">
|
||||
(has existing value)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ai-suggestion-value">{suggestions.title}</div>
|
||||
{currentValues.title && (
|
||||
<div className="ai-suggestion-current">
|
||||
Current: <em>{currentValues.title}</em>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{suggestions?.alt && (
|
||||
<div className="ai-suggestion-item">
|
||||
<label className="ai-suggestion-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useAlt}
|
||||
onChange={(e) => setUseAlt(e.target.checked)}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
</label>
|
||||
<div className="ai-suggestion-content">
|
||||
<div className="ai-suggestion-label">
|
||||
Alt Text
|
||||
{currentValues.alt && (
|
||||
<span className="ai-suggestion-has-value" title="This field already has a value">
|
||||
(has existing value)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ai-suggestion-value">{suggestions.alt}</div>
|
||||
{currentValues.alt && (
|
||||
<div className="ai-suggestion-current">
|
||||
Current: <em>{currentValues.alt}</em>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{suggestions?.caption && (
|
||||
<div className="ai-suggestion-item">
|
||||
<label className="ai-suggestion-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useCaption}
|
||||
onChange={(e) => setUseCaption(e.target.checked)}
|
||||
/>
|
||||
<span className="checkmark"></span>
|
||||
</label>
|
||||
<div className="ai-suggestion-content">
|
||||
<div className="ai-suggestion-label">
|
||||
Caption
|
||||
{currentValues.caption && (
|
||||
<span className="ai-suggestion-has-value" title="This field already has a value">
|
||||
(has existing value)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="ai-suggestion-value">{suggestions.caption}</div>
|
||||
{currentValues.caption && (
|
||||
<div className="ai-suggestion-current">
|
||||
Current: <em>{currentValues.caption}</em>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{SUGGESTION_FIELDS.map(renderSuggestionField)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { showToast } from '../Toast';
|
||||
import { getContrastColor } from '../../utils/color';
|
||||
import { subscribeToTagEvents } from '../../utils/tagEventSubscriptions';
|
||||
import './TagInput.css';
|
||||
|
||||
interface TagData {
|
||||
@@ -54,24 +55,7 @@ export const TagInput: React.FC<TagInputProps> = ({
|
||||
|
||||
// Listen for tag changes
|
||||
useEffect(() => {
|
||||
const unsubscribers: Array<() => void> = [];
|
||||
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:created', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:deleted', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:renamed', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tags:merged', () => loadTags()) || (() => {})
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribers.forEach(unsub => unsub());
|
||||
};
|
||||
return subscribeToTagEvents(window.electronAPI?.on, loadTags);
|
||||
}, [loadTags]);
|
||||
|
||||
// Filter suggestions based on input
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useAppStore } from '../../store';
|
||||
import { showToast } from '../Toast';
|
||||
import { getContrastColor } from '../../utils/color';
|
||||
import { subscribeToTagEvents } from '../../utils/tagEventSubscriptions';
|
||||
import './TagsView.css';
|
||||
|
||||
// Types
|
||||
@@ -179,27 +180,9 @@ export const TagsView: React.FC = () => {
|
||||
|
||||
// Listen for tag events
|
||||
useEffect(() => {
|
||||
const unsubscribers: Array<() => void> = [];
|
||||
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:created', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:updated', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:deleted', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tag:renamed', () => loadTags()) || (() => {})
|
||||
);
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('tags:merged', () => loadTags()) || (() => {})
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribers.forEach(unsub => unsub());
|
||||
};
|
||||
return subscribeToTagEvents(window.electronAPI?.on, loadTags, {
|
||||
includeUpdated: true,
|
||||
});
|
||||
}, [loadTags]);
|
||||
|
||||
// Handle tag selection
|
||||
|
||||
27
src/renderer/utils/tagEventSubscriptions.ts
Normal file
27
src/renderer/utils/tagEventSubscriptions.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
type ElectronOn = ((channel: string, callback: (...args: unknown[]) => void) => (() => void) | void) | undefined;
|
||||
|
||||
interface SubscribeTagEventsOptions {
|
||||
includeUpdated?: boolean;
|
||||
}
|
||||
|
||||
const BASE_TAG_EVENTS = ['tag:created', 'tag:deleted', 'tag:renamed', 'tags:merged'] as const;
|
||||
|
||||
export function subscribeToTagEvents(
|
||||
on: ElectronOn,
|
||||
callback: () => void,
|
||||
options: SubscribeTagEventsOptions = {}
|
||||
): () => void {
|
||||
if (!on) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const channels = options.includeUpdated
|
||||
? [...BASE_TAG_EVENTS, 'tag:updated']
|
||||
: BASE_TAG_EVENTS;
|
||||
|
||||
const unsubscribers = channels.map((channel) => on(channel, callback) || (() => {}));
|
||||
|
||||
return () => {
|
||||
unsubscribers.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user