mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
feat(config): add YAML diff confirmation before save
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
@use '../../styles/variables' as *;
|
||||
@use '../../styles/mixins' as *;
|
||||
|
||||
.diffModal {
|
||||
:global(.modal-body) {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-sm;
|
||||
height: 70vh;
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.columnLabels {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: $spacing-sm;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
|
||||
span {
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.mergeRoot {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: $radius-md;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
|
||||
:global(.cm-mergeView) {
|
||||
height: 100%;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
:global(.cm-mergeViewEditors),
|
||||
:global(.cm-mergeViewEditor),
|
||||
:global(.cm-editor) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global(.cm-scroller) {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:global(.cm-gutters) {
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
:global(.cm-mergeGap) {
|
||||
background: var(--bg-secondary);
|
||||
border-left: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
.content {
|
||||
height: 65vh;
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { MergeView } from '@codemirror/merge';
|
||||
import { Modal } from '@/components/ui/Modal';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { useThemeStore } from '@/stores';
|
||||
import styles from './DiffModal.module.scss';
|
||||
|
||||
type DiffModalProps = {
|
||||
open: boolean;
|
||||
original: string;
|
||||
modified: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
export function DiffModal({
|
||||
open,
|
||||
original,
|
||||
modified,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
loading = false
|
||||
}: DiffModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const resolvedTheme = useThemeStore((state) => state.resolvedTheme);
|
||||
const mergeContainerRef = useRef<HTMLDivElement>(null);
|
||||
const mergeViewRef = useRef<MergeView | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !mergeContainerRef.current) return;
|
||||
|
||||
const mountEl = mergeContainerRef.current;
|
||||
mountEl.innerHTML = '';
|
||||
|
||||
const commonExtensions = [
|
||||
yaml(),
|
||||
EditorState.readOnly.of(true),
|
||||
EditorView.editable.of(false),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.theme(
|
||||
{
|
||||
'&': { height: '100%' },
|
||||
'.cm-scroller': {
|
||||
fontFamily: "'Consolas', 'Monaco', 'Menlo', monospace"
|
||||
}
|
||||
},
|
||||
{ dark: resolvedTheme === 'dark' }
|
||||
)
|
||||
];
|
||||
|
||||
const view = new MergeView({
|
||||
parent: mountEl,
|
||||
a: { doc: original, extensions: commonExtensions },
|
||||
b: { doc: modified, extensions: commonExtensions },
|
||||
orientation: 'a-b',
|
||||
highlightChanges: true,
|
||||
gutter: true
|
||||
});
|
||||
mergeViewRef.current = view;
|
||||
|
||||
return () => {
|
||||
view.destroy();
|
||||
if (mergeViewRef.current === view) {
|
||||
mergeViewRef.current = null;
|
||||
}
|
||||
mountEl.innerHTML = '';
|
||||
};
|
||||
}, [modified, open, original, resolvedTheme]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={t('config_management.diff.title')}
|
||||
onClose={onCancel}
|
||||
width="min(1200px, 90vw)"
|
||||
className={styles.diffModal}
|
||||
closeDisabled={loading}
|
||||
footer={
|
||||
<>
|
||||
<Button variant="secondary" onClick={onCancel} disabled={loading}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} loading={loading} disabled={loading}>
|
||||
{t('config_management.diff.confirm')}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.columnLabels}>
|
||||
<span>{t('config_management.diff.current')}</span>
|
||||
<span>{t('config_management.diff.modified')}</span>
|
||||
</div>
|
||||
<div className={styles.mergeRoot} ref={mergeContainerRef} />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user