mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 18:50:49 +08:00
fix(model-alias): restore diagram drag-and-drop and add touch tap-to-link fallback
This commit is contained in:
@@ -7,6 +7,16 @@
|
|||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tapHint {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 3;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 0 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -100,6 +110,12 @@
|
|||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mindmap-style provider branch (root node)
|
// Mindmap-style provider branch (root node)
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const resolvedTheme = useThemeStore((state) => state.resolvedTheme);
|
const resolvedTheme = useThemeStore((state) => state.resolvedTheme);
|
||||||
const isDark = resolvedTheme === 'dark';
|
const isDark = resolvedTheme === 'dark';
|
||||||
|
const enableTapLinking = useMemo(() => {
|
||||||
|
if (typeof window === 'undefined' || typeof window.matchMedia === 'undefined') return false;
|
||||||
|
return (
|
||||||
|
window.matchMedia('(any-pointer: coarse)').matches &&
|
||||||
|
!window.matchMedia('(any-pointer: fine)').matches
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [lines, setLines] = useState<DiagramLine[]>([]);
|
const [lines, setLines] = useState<DiagramLine[]>([]);
|
||||||
@@ -69,6 +76,8 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
const [draggedAlias, setDraggedAlias] = useState<string | null>(null);
|
const [draggedAlias, setDraggedAlias] = useState<string | null>(null);
|
||||||
const [dropTargetAlias, setDropTargetAlias] = useState<string | null>(null);
|
const [dropTargetAlias, setDropTargetAlias] = useState<string | null>(null);
|
||||||
const [dropTargetSource, setDropTargetSource] = useState<string | null>(null);
|
const [dropTargetSource, setDropTargetSource] = useState<string | null>(null);
|
||||||
|
const [tapSourceId, setTapSourceId] = useState<string | null>(null);
|
||||||
|
const [tapAlias, setTapAlias] = useState<string | null>(null);
|
||||||
const [extraAliases, setExtraAliases] = useState<string[]>([]);
|
const [extraAliases, setExtraAliases] = useState<string[]>([]);
|
||||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||||
const [collapsedProviders, setCollapsedProviders] = useState<Set<string>>(new Set());
|
const [collapsedProviders, setCollapsedProviders] = useState<Set<string>>(new Set());
|
||||||
@@ -301,16 +310,18 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
// Drag and Drop handlers
|
// Drag and Drop handlers
|
||||||
// 1. Source -> Alias
|
// 1. Source -> Alias
|
||||||
const handleDragStart = (e: DragEvent, source: SourceNode) => {
|
const handleDragStart = (e: DragEvent, source: SourceNode) => {
|
||||||
|
setTapSourceId(null);
|
||||||
|
setTapAlias(null);
|
||||||
setDraggedSource(source);
|
setDraggedSource(source);
|
||||||
e.dataTransfer.setData('text/plain', source.id);
|
e.dataTransfer.setData('text/plain', source.id);
|
||||||
e.dataTransfer.effectAllowed = 'link';
|
e.dataTransfer.effectAllowed = 'link';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (e: DragEvent, alias: string) => {
|
const handleDragOver = (e: DragEvent, alias: string) => {
|
||||||
|
if (!draggedSource || draggedSource.aliases.some((entry) => entry.alias === alias)) return;
|
||||||
e.preventDefault(); // Allow drop
|
e.preventDefault(); // Allow drop
|
||||||
if (draggedSource && !draggedSource.aliases.some((entry) => entry.alias === alias)) {
|
e.dataTransfer.dropEffect = 'link';
|
||||||
setDropTargetAlias(alias);
|
setDropTargetAlias(alias);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragLeave = () => {
|
const handleDragLeave = () => {
|
||||||
@@ -328,16 +339,18 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
|
|
||||||
// 2. Alias -> Source
|
// 2. Alias -> Source
|
||||||
const handleDragStartAlias = (e: DragEvent, alias: string) => {
|
const handleDragStartAlias = (e: DragEvent, alias: string) => {
|
||||||
|
setTapSourceId(null);
|
||||||
|
setTapAlias(null);
|
||||||
setDraggedAlias(alias);
|
setDraggedAlias(alias);
|
||||||
e.dataTransfer.setData('text/plain', alias);
|
e.dataTransfer.setData('text/plain', alias);
|
||||||
e.dataTransfer.effectAllowed = 'link';
|
e.dataTransfer.effectAllowed = 'link';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOverSource = (e: DragEvent, source: SourceNode) => {
|
const handleDragOverSource = (e: DragEvent, source: SourceNode) => {
|
||||||
|
if (!draggedAlias || source.aliases.some((entry) => entry.alias === draggedAlias)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (draggedAlias && !source.aliases.some((entry) => entry.alias === draggedAlias)) {
|
e.dataTransfer.dropEffect = 'link';
|
||||||
setDropTargetSource(source.id);
|
setDropTargetSource(source.id);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragLeaveSource = () => {
|
const handleDragLeaveSource = () => {
|
||||||
@@ -382,6 +395,45 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
[providerNodes]
|
[providerNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleTapSelectSource = (source: SourceNode) => {
|
||||||
|
if (!onUpdate) return;
|
||||||
|
if (tapSourceId === source.id) {
|
||||||
|
setTapSourceId(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tapAlias) {
|
||||||
|
onUpdate(source.provider, source.name, tapAlias);
|
||||||
|
setTapSourceId(null);
|
||||||
|
setTapAlias(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTapSourceId(source.id);
|
||||||
|
setTapAlias(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTapSelectAlias = (alias: string) => {
|
||||||
|
if (!onUpdate) return;
|
||||||
|
if (tapAlias === alias) {
|
||||||
|
setTapAlias(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tapSourceId) {
|
||||||
|
const source = resolveSourceById(tapSourceId);
|
||||||
|
if (source) {
|
||||||
|
onUpdate(source.provider, source.name, alias);
|
||||||
|
}
|
||||||
|
setTapSourceId(null);
|
||||||
|
setTapAlias(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTapAlias(alias);
|
||||||
|
setTapSourceId(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleUnlinkSource = (provider: string, sourceModel: string, alias: string) => {
|
const handleUnlinkSource = (provider: string, sourceModel: string, alias: string) => {
|
||||||
if (onDeleteLink) onDeleteLink(provider, sourceModel, alias);
|
if (onDeleteLink) onDeleteLink(provider, sourceModel, alias);
|
||||||
};
|
};
|
||||||
@@ -459,6 +511,9 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.scrollContainer, className].filter(Boolean).join(' ')}>
|
<div className={[styles.scrollContainer, className].filter(Boolean).join(' ')}>
|
||||||
|
{enableTapLinking && onUpdate && (
|
||||||
|
<div className={styles.tapHint}>{t('oauth_model_alias.diagram_tap_hint')}</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
@@ -480,15 +535,15 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<ProviderColumn
|
<ProviderColumn
|
||||||
providerNodes={providerNodes}
|
providerNodes={providerNodes}
|
||||||
collapsedProviders={collapsedProviders}
|
collapsedProviders={collapsedProviders}
|
||||||
getProviderColor={getProviderColor}
|
getProviderColor={getProviderColor}
|
||||||
providerGroupHeights={providerGroupHeights}
|
providerGroupHeights={providerGroupHeights}
|
||||||
providerRefs={providerRefs}
|
providerRefs={providerRefs}
|
||||||
onToggleCollapse={toggleProviderCollapse}
|
onToggleCollapse={toggleProviderCollapse}
|
||||||
onContextMenu={(e, type, data) => handleContextMenu(e, type, data)}
|
onContextMenu={(e, type, data) => handleContextMenu(e, type, data)}
|
||||||
label={t('oauth_model_alias.diagram_providers')}
|
label={t('oauth_model_alias.diagram_providers')}
|
||||||
expandLabel={t('oauth_model_alias.diagram_expand')}
|
expandLabel={t('oauth_model_alias.diagram_expand')}
|
||||||
collapseLabel={t('oauth_model_alias.diagram_collapse')}
|
collapseLabel={t('oauth_model_alias.diagram_collapse')}
|
||||||
/>
|
/>
|
||||||
<SourceColumn
|
<SourceColumn
|
||||||
@@ -496,6 +551,8 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
collapsedProviders={collapsedProviders}
|
collapsedProviders={collapsedProviders}
|
||||||
sourceRefs={sourceRefs}
|
sourceRefs={sourceRefs}
|
||||||
getProviderColor={getProviderColor}
|
getProviderColor={getProviderColor}
|
||||||
|
selectedSourceId={enableTapLinking ? tapSourceId : null}
|
||||||
|
onSelectSource={enableTapLinking ? handleTapSelectSource : undefined}
|
||||||
draggedSource={draggedSource}
|
draggedSource={draggedSource}
|
||||||
dropTargetSource={dropTargetSource}
|
dropTargetSource={dropTargetSource}
|
||||||
draggable={!!onUpdate}
|
draggable={!!onUpdate}
|
||||||
@@ -515,6 +572,8 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
aliasRefs={aliasRefs}
|
aliasRefs={aliasRefs}
|
||||||
dropTargetAlias={dropTargetAlias}
|
dropTargetAlias={dropTargetAlias}
|
||||||
draggedAlias={draggedAlias}
|
draggedAlias={draggedAlias}
|
||||||
|
selectedAlias={enableTapLinking ? tapAlias : null}
|
||||||
|
onSelectAlias={enableTapLinking ? handleTapSelectAlias : undefined}
|
||||||
draggable={!!onUpdate}
|
draggable={!!onUpdate}
|
||||||
onDragStart={handleDragStartAlias}
|
onDragStart={handleDragStartAlias}
|
||||||
onDragEnd={() => {
|
onDragEnd={() => {
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ interface SourceColumnProps {
|
|||||||
collapsedProviders: Set<string>;
|
collapsedProviders: Set<string>;
|
||||||
sourceRefs: RefObject<Map<string, HTMLDivElement>>;
|
sourceRefs: RefObject<Map<string, HTMLDivElement>>;
|
||||||
getProviderColor: (provider: string) => string;
|
getProviderColor: (provider: string) => string;
|
||||||
|
selectedSourceId?: string | null;
|
||||||
|
onSelectSource?: (source: SourceNode) => void;
|
||||||
draggedSource: SourceNode | null;
|
draggedSource: SourceNode | null;
|
||||||
dropTargetSource: string | null;
|
dropTargetSource: string | null;
|
||||||
draggable: boolean;
|
draggable: boolean;
|
||||||
@@ -102,6 +104,8 @@ export function SourceColumn({
|
|||||||
collapsedProviders,
|
collapsedProviders,
|
||||||
sourceRefs,
|
sourceRefs,
|
||||||
getProviderColor,
|
getProviderColor,
|
||||||
|
selectedSourceId,
|
||||||
|
onSelectSource,
|
||||||
draggedSource,
|
draggedSource,
|
||||||
dropTargetSource,
|
dropTargetSource,
|
||||||
draggable,
|
draggable,
|
||||||
@@ -134,7 +138,10 @@ export function SourceColumn({
|
|||||||
}}
|
}}
|
||||||
className={`${styles.item} ${styles.sourceItem} ${
|
className={`${styles.item} ${styles.sourceItem} ${
|
||||||
draggedSource?.id === source.id ? styles.dragging : ''
|
draggedSource?.id === source.id ? styles.dragging : ''
|
||||||
} ${dropTargetSource === source.id ? styles.dropTarget : ''}`}
|
} ${dropTargetSource === source.id ? styles.dropTarget : ''} ${
|
||||||
|
selectedSourceId === source.id ? styles.selected : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => onSelectSource?.(source)}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onDragStart={(e) => onDragStart(e, source)}
|
onDragStart={(e) => onDragStart(e, source)}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
@@ -169,6 +176,8 @@ interface AliasColumnProps {
|
|||||||
aliasRefs: RefObject<Map<string, HTMLDivElement>>;
|
aliasRefs: RefObject<Map<string, HTMLDivElement>>;
|
||||||
dropTargetAlias: string | null;
|
dropTargetAlias: string | null;
|
||||||
draggedAlias: string | null;
|
draggedAlias: string | null;
|
||||||
|
selectedAlias?: string | null;
|
||||||
|
onSelectAlias?: (alias: string) => void;
|
||||||
draggable: boolean;
|
draggable: boolean;
|
||||||
onDragStart: (e: DragEvent, alias: string) => void;
|
onDragStart: (e: DragEvent, alias: string) => void;
|
||||||
onDragEnd: () => void;
|
onDragEnd: () => void;
|
||||||
@@ -184,6 +193,8 @@ export function AliasColumn({
|
|||||||
aliasRefs,
|
aliasRefs,
|
||||||
dropTargetAlias,
|
dropTargetAlias,
|
||||||
draggedAlias,
|
draggedAlias,
|
||||||
|
selectedAlias,
|
||||||
|
onSelectAlias,
|
||||||
draggable,
|
draggable,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
@@ -212,7 +223,10 @@ export function AliasColumn({
|
|||||||
}}
|
}}
|
||||||
className={`${styles.item} ${styles.aliasItem} ${
|
className={`${styles.item} ${styles.aliasItem} ${
|
||||||
dropTargetAlias === node.alias ? styles.dropTarget : ''
|
dropTargetAlias === node.alias ? styles.dropTarget : ''
|
||||||
} ${draggedAlias === node.alias ? styles.dragging : ''}`}
|
} ${draggedAlias === node.alias ? styles.dragging : ''} ${
|
||||||
|
selectedAlias === node.alias ? styles.selected : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => onSelectAlias?.(node.alias)}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onDragStart={(e) => onDragStart(e, node.alias)}
|
onDragStart={(e) => onDragStart(e, node.alias)}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
|
|||||||
@@ -577,6 +577,7 @@
|
|||||||
"diagram_settings_title": "Alias settings — {{alias}}",
|
"diagram_settings_title": "Alias settings — {{alias}}",
|
||||||
"diagram_settings_source_title": "Source model settings",
|
"diagram_settings_source_title": "Source model settings",
|
||||||
"diagram_settings_empty": "No mappings for this alias yet.",
|
"diagram_settings_empty": "No mappings for this alias yet.",
|
||||||
|
"diagram_tap_hint": "On touch devices: tap a source model, then tap an alias to link.",
|
||||||
"view_mode": "View mode",
|
"view_mode": "View mode",
|
||||||
"view_mode_diagram": "Diagram",
|
"view_mode_diagram": "Diagram",
|
||||||
"view_mode_list": "List",
|
"view_mode_list": "List",
|
||||||
|
|||||||
@@ -577,6 +577,7 @@
|
|||||||
"diagram_settings_title": "别名设置 — {{alias}}",
|
"diagram_settings_title": "别名设置 — {{alias}}",
|
||||||
"diagram_settings_source_title": "源模型设置",
|
"diagram_settings_source_title": "源模型设置",
|
||||||
"diagram_settings_empty": "该别名暂无映射。",
|
"diagram_settings_empty": "该别名暂无映射。",
|
||||||
|
"diagram_tap_hint": "触摸设备上:先点选源模型,再点选别名即可建立映射。",
|
||||||
"view_mode": "视图模式",
|
"view_mode": "视图模式",
|
||||||
"view_mode_diagram": "概览",
|
"view_mode_diagram": "概览",
|
||||||
"view_mode_list": "管理",
|
"view_mode_list": "管理",
|
||||||
|
|||||||
Reference in New Issue
Block a user