mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 11:10:49 +08:00
fix(model-alias): improve diagram mobile layout and refresh reliability
This commit is contained in:
@@ -17,8 +17,8 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
justify-content: flex-start;
|
// Give mobile extra horizontal room to reduce line overlap; users can swipe to scroll.
|
||||||
gap: 16px;
|
min-width: max(100%, 960px);
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,6 +158,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.providerGroup {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.sourceItem,
|
.sourceItem,
|
||||||
.aliasItem {
|
.aliasItem {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
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());
|
||||||
|
const [providerGroupHeights, setProviderGroupHeights] = useState<Record<string, number>>({});
|
||||||
const [renameState, setRenameState] = useState<{ oldAlias: string } | null>(null);
|
const [renameState, setRenameState] = useState<{ oldAlias: string } | null>(null);
|
||||||
const [renameValue, setRenameValue] = useState('');
|
const [renameValue, setRenameValue] = useState('');
|
||||||
const [renameError, setRenameError] = useState('');
|
const [renameError, setRenameError] = useState('');
|
||||||
@@ -179,6 +180,7 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
if (!containerRef.current) return;
|
if (!containerRef.current) return;
|
||||||
const containerRect = containerRef.current.getBoundingClientRect();
|
const containerRect = containerRef.current.getBoundingClientRect();
|
||||||
const newLines: { path: string; color: string; id: string }[] = [];
|
const newLines: { path: string; color: string; id: string }[] = [];
|
||||||
|
const nextProviderGroupHeights: Record<string, number> = {};
|
||||||
|
|
||||||
const bezier = (
|
const bezier = (
|
||||||
x1: number, y1: number,
|
x1: number, y1: number,
|
||||||
@@ -193,6 +195,15 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
const collapsed = collapsedProviders.has(provider);
|
const collapsed = collapsedProviders.has(provider);
|
||||||
if (collapsed) return;
|
if (collapsed) return;
|
||||||
|
|
||||||
|
if (sources.length > 0) {
|
||||||
|
const firstEl = sourceRefs.current.get(sources[0].id);
|
||||||
|
const lastEl = sourceRefs.current.get(sources[sources.length - 1].id);
|
||||||
|
if (firstEl && lastEl) {
|
||||||
|
const height = Math.max(0, Math.round(lastEl.getBoundingClientRect().bottom - firstEl.getBoundingClientRect().top));
|
||||||
|
if (height > 0) nextProviderGroupHeights[provider] = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const providerEl = providerRefs.current.get(provider);
|
const providerEl = providerRefs.current.get(provider);
|
||||||
if (!providerEl) return;
|
if (!providerEl) return;
|
||||||
const providerRect = providerEl.getBoundingClientRect();
|
const providerRect = providerEl.getBoundingClientRect();
|
||||||
@@ -241,6 +252,17 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
});
|
});
|
||||||
|
|
||||||
setLines(newLines);
|
setLines(newLines);
|
||||||
|
setProviderGroupHeights((prev) => {
|
||||||
|
const prevKeys = Object.keys(prev);
|
||||||
|
const nextKeys = Object.keys(nextProviderGroupHeights);
|
||||||
|
if (prevKeys.length !== nextKeys.length) return nextProviderGroupHeights;
|
||||||
|
for (const key of nextKeys) {
|
||||||
|
if (!(key in prev) || prev[key] !== nextProviderGroupHeights[key]) {
|
||||||
|
return nextProviderGroupHeights;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
}, [providerNodes, collapsedProviders]);
|
}, [providerNodes, collapsedProviders]);
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
@@ -263,6 +285,12 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
};
|
};
|
||||||
}, [updateLines, aliasNodes]);
|
}, [updateLines, aliasNodes]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
updateLines();
|
||||||
|
const raf = requestAnimationFrame(updateLines);
|
||||||
|
return () => cancelAnimationFrame(raf);
|
||||||
|
}, [providerGroupHeights, updateLines]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current || typeof ResizeObserver === 'undefined') return;
|
if (!containerRef.current || typeof ResizeObserver === 'undefined') return;
|
||||||
const observer = new ResizeObserver(() => updateLines());
|
const observer = new ResizeObserver(() => updateLines());
|
||||||
@@ -452,14 +480,15 @@ export const ModelMappingDiagram = forwardRef<ModelMappingDiagramRef, ModelMappi
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<ProviderColumn
|
<ProviderColumn
|
||||||
providerNodes={providerNodes}
|
providerNodes={providerNodes}
|
||||||
collapsedProviders={collapsedProviders}
|
collapsedProviders={collapsedProviders}
|
||||||
getProviderColor={getProviderColor}
|
getProviderColor={getProviderColor}
|
||||||
providerRefs={providerRefs}
|
providerGroupHeights={providerGroupHeights}
|
||||||
onToggleCollapse={toggleProviderCollapse}
|
providerRefs={providerRefs}
|
||||||
onContextMenu={(e, type, data) => handleContextMenu(e, type, data)}
|
onToggleCollapse={toggleProviderCollapse}
|
||||||
label={t('oauth_model_alias.diagram_providers')}
|
onContextMenu={(e, type, data) => handleContextMenu(e, type, data)}
|
||||||
expandLabel={t('oauth_model_alias.diagram_expand')}
|
label={t('oauth_model_alias.diagram_providers')}
|
||||||
|
expandLabel={t('oauth_model_alias.diagram_expand')}
|
||||||
collapseLabel={t('oauth_model_alias.diagram_collapse')}
|
collapseLabel={t('oauth_model_alias.diagram_collapse')}
|
||||||
/>
|
/>
|
||||||
<SourceColumn
|
<SourceColumn
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface ProviderColumnProps {
|
|||||||
providerNodes: ProviderNode[];
|
providerNodes: ProviderNode[];
|
||||||
collapsedProviders: Set<string>;
|
collapsedProviders: Set<string>;
|
||||||
getProviderColor: (provider: string) => string;
|
getProviderColor: (provider: string) => string;
|
||||||
|
providerGroupHeights?: Record<string, number>;
|
||||||
providerRefs: RefObject<Map<string, HTMLDivElement>>;
|
providerRefs: RefObject<Map<string, HTMLDivElement>>;
|
||||||
onToggleCollapse: (provider: string) => void;
|
onToggleCollapse: (provider: string) => void;
|
||||||
onContextMenu: (e: ReactMouseEvent, type: 'provider' | 'background', data?: string) => void;
|
onContextMenu: (e: ReactMouseEvent, type: 'provider' | 'background', data?: string) => void;
|
||||||
@@ -18,6 +19,7 @@ export function ProviderColumn({
|
|||||||
providerNodes,
|
providerNodes,
|
||||||
collapsedProviders,
|
collapsedProviders,
|
||||||
getProviderColor,
|
getProviderColor,
|
||||||
|
providerGroupHeights = {},
|
||||||
providerRefs,
|
providerRefs,
|
||||||
onToggleCollapse,
|
onToggleCollapse,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
@@ -37,34 +39,40 @@ export function ProviderColumn({
|
|||||||
<div className={styles.columnHeader}>{label}</div>
|
<div className={styles.columnHeader}>{label}</div>
|
||||||
{providerNodes.map(({ provider, sources }) => {
|
{providerNodes.map(({ provider, sources }) => {
|
||||||
const collapsed = collapsedProviders.has(provider);
|
const collapsed = collapsedProviders.has(provider);
|
||||||
|
const groupHeight = collapsed ? undefined : providerGroupHeights[provider];
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={provider}
|
key={provider}
|
||||||
ref={(el) => {
|
className={styles.providerGroup}
|
||||||
if (el) providerRefs.current?.set(provider, el);
|
style={groupHeight ? { height: groupHeight } : undefined}
|
||||||
else providerRefs.current?.delete(provider);
|
|
||||||
}}
|
|
||||||
className={`${styles.item} ${styles.providerItem}`}
|
|
||||||
style={{ borderLeftColor: getProviderColor(provider) }}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
onContextMenu(e, 'provider', provider);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
type="button"
|
ref={(el) => {
|
||||||
className={styles.collapseBtn}
|
if (el) providerRefs.current?.set(provider, el);
|
||||||
onClick={() => onToggleCollapse(provider)}
|
else providerRefs.current?.delete(provider);
|
||||||
aria-label={collapsed ? expandLabel : collapseLabel}
|
}}
|
||||||
title={collapsed ? expandLabel : collapseLabel}
|
className={`${styles.item} ${styles.providerItem}`}
|
||||||
|
style={{ borderLeftColor: getProviderColor(provider) }}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onContextMenu(e, 'provider', provider);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className={collapsed ? styles.chevronRight : styles.chevronDown} />
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<span className={styles.providerLabel} style={{ color: getProviderColor(provider) }}>
|
className={styles.collapseBtn}
|
||||||
{provider}
|
onClick={() => onToggleCollapse(provider)}
|
||||||
</span>
|
aria-label={collapsed ? expandLabel : collapseLabel}
|
||||||
<span className={styles.itemCount}>{sources.length}</span>
|
title={collapsed ? expandLabel : collapseLabel}
|
||||||
|
>
|
||||||
|
<span className={collapsed ? styles.chevronRight : styles.chevronDown} />
|
||||||
|
</button>
|
||||||
|
<span className={styles.providerLabel} style={{ color: getProviderColor(provider) }}>
|
||||||
|
{provider}
|
||||||
|
</span>
|
||||||
|
<span className={styles.itemCount}>{sources.length}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user