mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-08 23:53:30 +08:00
✨ feat(query-editor): 记住查询编辑区结果区比例
- 持久化查询编辑器与结果面板的分割比例 - 新开查询 Tab 时按已保存比例恢复编辑区高度 - 补充分割比例计算与 QueryEditor 回归测试 Fixes #538
This commit is contained in:
@@ -57,6 +57,7 @@ const storeState = vi.hoisted(() => ({
|
||||
showColumnComment: true,
|
||||
showColumnType: true,
|
||||
showQueryResultsPanel: false,
|
||||
queryEditorEditorHeightRatio: 0.5,
|
||||
},
|
||||
setQueryOptions: vi.fn(),
|
||||
sqlEditorTransactionOptions: {
|
||||
@@ -581,6 +582,29 @@ const createDefaultConnections = () => ([
|
||||
},
|
||||
]);
|
||||
|
||||
const createQueryEditorSplitNodeMock = (element: any) => {
|
||||
const className = String(element?.props?.className || '');
|
||||
if (className.includes('gn-v2-query-monaco-shell')) {
|
||||
return {
|
||||
style: {},
|
||||
getBoundingClientRect: () => ({ height: 300 }),
|
||||
};
|
||||
}
|
||||
if (className.includes('gn-v2-query-editor-pane')) {
|
||||
return {
|
||||
style: {},
|
||||
getBoundingClientRect: () => ({ height: 405 }),
|
||||
};
|
||||
}
|
||||
if (className.includes('gn-v2-query-editor')) {
|
||||
return {
|
||||
style: {},
|
||||
getBoundingClientRect: () => ({ height: 805 }),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
describe('QueryEditor external SQL save', () => {
|
||||
beforeEach(() => {
|
||||
const completionState = (globalThis as any).__gonaviSqlCompletionState;
|
||||
@@ -619,11 +643,13 @@ describe('QueryEditor external SQL save', () => {
|
||||
storeState.activeTabId = 'tab-1';
|
||||
storeState.aiPanelVisible = false;
|
||||
storeState.setAIPanelVisible.mockReset();
|
||||
storeState.appearance.uiVersion = 'legacy';
|
||||
storeState.queryOptions = {
|
||||
maxRows: 5000,
|
||||
showColumnComment: true,
|
||||
showColumnType: true,
|
||||
showQueryResultsPanel: false,
|
||||
queryEditorEditorHeightRatio: 0.5,
|
||||
};
|
||||
storeState.sqlEditorTransactionOptions = {
|
||||
commitMode: 'manual',
|
||||
@@ -8187,6 +8213,11 @@ describe('QueryEditor external SQL save', () => {
|
||||
await act(async () => {
|
||||
renderer = create(<QueryEditor tab={createTab({ resultPanelVisible: true })} />);
|
||||
});
|
||||
await act(async () => {
|
||||
frameCallbacks.splice(0).forEach((callback) => callback(0));
|
||||
});
|
||||
vi.mocked(window.requestAnimationFrame).mockClear();
|
||||
editorState.editor.layout.mockClear();
|
||||
|
||||
const resizer = renderer.root.find((node) => node.props?.title === '拖动调整高度');
|
||||
await act(async () => {
|
||||
@@ -8211,6 +8242,60 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(document.removeEventListener).toHaveBeenCalledWith('mouseup', expect.any(Function));
|
||||
});
|
||||
|
||||
it('persists the editor and result panel split ratio after dragging the splitter', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
const moveListeners: Array<(event: MouseEvent) => void> = [];
|
||||
const upListeners: Array<() => void> = [];
|
||||
vi.mocked(document.addEventListener).mockImplementation((type: string, listener: any) => {
|
||||
if (type === 'mousemove') moveListeners.push(listener);
|
||||
if (type === 'mouseup') upListeners.push(listener);
|
||||
});
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<QueryEditor tab={createTab({ resultPanelVisible: true })} />,
|
||||
{ createNodeMock: createQueryEditorSplitNodeMock },
|
||||
);
|
||||
});
|
||||
|
||||
const resizer = renderer.root.find((node) => node.props?.title === '拖动调整高度');
|
||||
await act(async () => {
|
||||
resizer.props.onMouseDown({ clientY: 300, preventDefault: vi.fn() });
|
||||
moveListeners.forEach((listener) => listener({ clientY: 420 } as MouseEvent));
|
||||
});
|
||||
await act(async () => {
|
||||
upListeners.forEach((listener) => listener());
|
||||
});
|
||||
|
||||
expect(storeState.setQueryOptions).toHaveBeenCalledWith({
|
||||
queryEditorEditorHeightRatio: 0.6,
|
||||
});
|
||||
});
|
||||
|
||||
it('applies the persisted editor and result split ratio when opening another query tab', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
storeState.activeTabId = 'tab-2';
|
||||
storeState.queryOptions = {
|
||||
...storeState.queryOptions,
|
||||
queryEditorEditorHeightRatio: 0.75,
|
||||
};
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<QueryEditor tab={createTab({ id: 'tab-2', resultPanelVisible: true })} />,
|
||||
{ createNodeMock: createQueryEditorSplitNodeMock },
|
||||
);
|
||||
});
|
||||
|
||||
const editorShell = renderer.root.find((node) => {
|
||||
const className = String(node.props?.className || '');
|
||||
return className.includes('gn-v2-query-monaco-shell');
|
||||
});
|
||||
expect(editorShell.props.style.height).toBe(525);
|
||||
});
|
||||
|
||||
it('inserts sidebar object text when dropped into the SQL editor', async () => {
|
||||
const domListeners: Record<string, ((event?: any) => void)[]> = {};
|
||||
editorState.domNode = {
|
||||
|
||||
@@ -36,6 +36,12 @@ import { t as translate } from '../i18n';
|
||||
import { buildSqlAnalysisWorkbenchTab } from '../utils/sqlAnalysisTab';
|
||||
import { isLocalizedUntitledQueryTitle } from '../utils/queryTabTitle';
|
||||
import { buildSqlServerObjectDefinitionQueries } from '../utils/sqlServerObjectDefinition';
|
||||
import {
|
||||
clampQueryEditorEditorHeight,
|
||||
resolveQueryEditorEditorHeightFromRatio,
|
||||
resolveQueryEditorEditorHeightRatio,
|
||||
sanitizeQueryEditorEditorHeightRatio,
|
||||
} from '../utils/queryEditorSplitLayout';
|
||||
import {
|
||||
DUCKDB_ROWID_LOCATOR_COLUMN,
|
||||
ORACLE_ROWID_LOCATOR_COLUMN,
|
||||
@@ -783,6 +789,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
const setSqlFormatOptions = useStore(state => state.setSqlFormatOptions);
|
||||
const queryOptions = useStore(state => state.queryOptions);
|
||||
const setQueryOptions = useStore(state => state.setQueryOptions);
|
||||
const queryEditorEditorHeightRatio = sanitizeQueryEditorEditorHeightRatio(
|
||||
queryOptions?.queryEditorEditorHeightRatio,
|
||||
);
|
||||
const sqlEditorTransactionOptions = useStore(state => state.sqlEditorTransactionOptions);
|
||||
const setSqlEditorTransactionOptions = useStore(state => state.setSqlEditorTransactionOptions);
|
||||
const [isResultPanelVisible, setIsResultPanelVisible] = useState(
|
||||
@@ -1801,11 +1810,78 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
setCurrentQueryId('');
|
||||
};
|
||||
|
||||
const resolveEditorSplitAvailableHeight = useCallback(() => {
|
||||
const rootRect = queryEditorRootRef.current?.getBoundingClientRect?.();
|
||||
const paneRect = editorPaneRef.current?.getBoundingClientRect?.();
|
||||
const shellRect = editorShellRef.current?.getBoundingClientRect?.();
|
||||
const rootHeight = Number(rootRect?.height || 0);
|
||||
const paneHeight = Number(paneRect?.height || 0);
|
||||
const shellHeight = Number(shellRect?.height || 0);
|
||||
if (!Number.isFinite(rootHeight) || rootHeight <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const nonEditorPaneHeight = paneHeight > 0 && shellHeight > 0
|
||||
? Math.max(0, paneHeight - shellHeight)
|
||||
: 0;
|
||||
const availableHeight = rootHeight - nonEditorPaneHeight;
|
||||
return Number.isFinite(availableHeight) && availableHeight > 0 ? availableHeight : 0;
|
||||
}, []);
|
||||
|
||||
const clampEditorHeight = useCallback((height: number) => {
|
||||
const availableHeight = resolveEditorSplitAvailableHeight();
|
||||
if (availableHeight > 0) {
|
||||
return clampQueryEditorEditorHeight(height, availableHeight);
|
||||
}
|
||||
const viewportHeight = Number.isFinite(window.innerHeight) ? window.innerHeight : 800;
|
||||
const maxHeight = Math.max(100, viewportHeight - 200);
|
||||
return Math.max(100, Math.min(maxHeight, height));
|
||||
}, []);
|
||||
}, [resolveEditorSplitAvailableHeight]);
|
||||
|
||||
const applyEditorHeightRatio = useCallback(() => {
|
||||
const availableHeight = resolveEditorSplitAvailableHeight();
|
||||
if (availableHeight <= 0 || dragRef.current) return;
|
||||
const nextHeight = resolveQueryEditorEditorHeightFromRatio(
|
||||
queryEditorEditorHeightRatio,
|
||||
availableHeight,
|
||||
);
|
||||
pendingEditorHeightRef.current = nextHeight;
|
||||
setEditorHeight(previousHeight => previousHeight === nextHeight ? previousHeight : nextHeight);
|
||||
}, [queryEditorEditorHeightRatio, resolveEditorSplitAvailableHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isResultPanelVisible || !isActive) return;
|
||||
let frame: number | null = null;
|
||||
const requestFrame = typeof window.requestAnimationFrame === 'function'
|
||||
? window.requestAnimationFrame.bind(window)
|
||||
: (callback: FrameRequestCallback) => window.setTimeout(() => callback(Date.now()), 16);
|
||||
const cancelFrame = typeof window.cancelAnimationFrame === 'function'
|
||||
? window.cancelAnimationFrame.bind(window)
|
||||
: window.clearTimeout.bind(window);
|
||||
const scheduleApply = () => {
|
||||
if (frame !== null) return;
|
||||
frame = requestFrame(() => {
|
||||
frame = null;
|
||||
applyEditorHeightRatio();
|
||||
});
|
||||
};
|
||||
|
||||
scheduleApply();
|
||||
const ResizeObserverCtor = typeof ResizeObserver === 'function' ? ResizeObserver : null;
|
||||
const resizeObserver = ResizeObserverCtor ? new ResizeObserverCtor(scheduleApply) : null;
|
||||
if (resizeObserver) {
|
||||
if (queryEditorRootRef.current) resizeObserver.observe(queryEditorRootRef.current);
|
||||
if (editorPaneRef.current) resizeObserver.observe(editorPaneRef.current);
|
||||
}
|
||||
window.addEventListener('resize', scheduleApply);
|
||||
return () => {
|
||||
if (frame !== null) {
|
||||
cancelFrame(frame);
|
||||
frame = null;
|
||||
}
|
||||
resizeObserver?.disconnect();
|
||||
window.removeEventListener('resize', scheduleApply);
|
||||
};
|
||||
}, [applyEditorHeightRatio, isActive, isResultPanelVisible, tab.id]);
|
||||
|
||||
const applyEditorHeightToDom = useCallback(() => {
|
||||
const nextHeight = pendingEditorHeightRef.current;
|
||||
@@ -1856,15 +1932,26 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
pendingEditorHeightRef.current = finalHeight;
|
||||
applyEditorHeightToDom();
|
||||
setEditorHeight(finalHeight);
|
||||
const availableHeight = resolveEditorSplitAvailableHeight();
|
||||
if (availableHeight > 0) {
|
||||
setQueryOptions({
|
||||
queryEditorEditorHeightRatio: resolveQueryEditorEditorHeightRatio(
|
||||
finalHeight,
|
||||
availableHeight,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
}, [applyEditorHeightToDom, cancelEditorResizeFrame, handleMouseMove]);
|
||||
}, [applyEditorHeightToDom, cancelEditorResizeFrame, handleMouseMove, resolveEditorSplitAvailableHeight, setQueryOptions]);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
dragRef.current = { startY: e.clientY, startHeight: editorHeight, currentHeight: editorHeight };
|
||||
pendingEditorHeightRef.current = editorHeight;
|
||||
const currentEditorHeight = Number(editorShellRef.current?.getBoundingClientRect?.().height || editorHeight);
|
||||
const startHeight = Number.isFinite(currentEditorHeight) && currentEditorHeight > 0 ? currentEditorHeight : editorHeight;
|
||||
dragRef.current = { startY: e.clientY, startHeight, currentHeight: startHeight };
|
||||
pendingEditorHeightRef.current = startHeight;
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
}, [editorHeight, handleMouseMove, handleMouseUp]);
|
||||
|
||||
@@ -78,6 +78,10 @@ import {
|
||||
normalizeConnectionProtectionConfig,
|
||||
resolveConnectionProtectionConfig,
|
||||
} from "./utils/connectionReadOnly";
|
||||
import {
|
||||
DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
sanitizeQueryEditorEditorHeightRatio,
|
||||
} from "./utils/queryEditorSplitLayout";
|
||||
|
||||
export type TableDoubleClickAction = "open-data" | "open-design";
|
||||
|
||||
@@ -1234,6 +1238,7 @@ export interface QueryOptions {
|
||||
showSidebarTableComment?: boolean;
|
||||
showColumnType: boolean;
|
||||
showQueryResultsPanel: boolean;
|
||||
queryEditorEditorHeightRatio: number;
|
||||
}
|
||||
|
||||
export interface DataEditTransactionOptions {
|
||||
@@ -1931,8 +1936,18 @@ const sanitizeQueryOptions = (value: unknown): QueryOptions => {
|
||||
typeof raw.showColumnType === "boolean" ? raw.showColumnType : true;
|
||||
const showQueryResultsPanel =
|
||||
typeof raw.showQueryResultsPanel === "boolean" ? raw.showQueryResultsPanel : false;
|
||||
const queryEditorEditorHeightRatio = sanitizeQueryEditorEditorHeightRatio(
|
||||
raw.queryEditorEditorHeightRatio,
|
||||
);
|
||||
if (!Number.isFinite(maxRows) || maxRows <= 0) {
|
||||
return { maxRows: 5000, showColumnComment, showSidebarTableComment, showColumnType, showQueryResultsPanel };
|
||||
return {
|
||||
maxRows: 5000,
|
||||
showColumnComment,
|
||||
showSidebarTableComment,
|
||||
showColumnType,
|
||||
showQueryResultsPanel,
|
||||
queryEditorEditorHeightRatio,
|
||||
};
|
||||
}
|
||||
return {
|
||||
maxRows: Math.min(50000, Math.trunc(maxRows)),
|
||||
@@ -1940,6 +1955,7 @@ const sanitizeQueryOptions = (value: unknown): QueryOptions => {
|
||||
showSidebarTableComment,
|
||||
showColumnType,
|
||||
showQueryResultsPanel,
|
||||
queryEditorEditorHeightRatio,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2370,6 +2386,7 @@ export const useStore = create<AppState>()(
|
||||
showSidebarTableComment: false,
|
||||
showColumnType: true,
|
||||
showQueryResultsPanel: false,
|
||||
queryEditorEditorHeightRatio: DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
},
|
||||
dataEditTransactionOptions: {
|
||||
commitMode: "manual",
|
||||
|
||||
43
frontend/src/utils/queryEditorSplitLayout.test.ts
Normal file
43
frontend/src/utils/queryEditorSplitLayout.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
MIN_QUERY_EDITOR_EDITOR_HEIGHT,
|
||||
MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
MIN_QUERY_EDITOR_RESULT_HEIGHT,
|
||||
clampQueryEditorEditorHeight,
|
||||
resolveQueryEditorEditorHeightFromRatio,
|
||||
resolveQueryEditorEditorHeightRatio,
|
||||
sanitizeQueryEditorEditorHeightRatio,
|
||||
} from './queryEditorSplitLayout';
|
||||
|
||||
describe('query editor split layout', () => {
|
||||
it('sanitizes persisted editor height ratios', () => {
|
||||
expect(sanitizeQueryEditorEditorHeightRatio(undefined)).toBe(DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
expect(sanitizeQueryEditorEditorHeightRatio(Number.NaN)).toBe(DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
expect(sanitizeQueryEditorEditorHeightRatio(0.01)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
expect(sanitizeQueryEditorEditorHeightRatio(0.99)).toBe(MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
expect(sanitizeQueryEditorEditorHeightRatio(0.62)).toBe(0.62);
|
||||
});
|
||||
|
||||
it('resolves editor height from a persisted ratio while leaving room for results', () => {
|
||||
expect(resolveQueryEditorEditorHeightFromRatio(0.5, 800)).toBe(400);
|
||||
expect(resolveQueryEditorEditorHeightFromRatio(0.9, 800)).toBe(680);
|
||||
expect(resolveQueryEditorEditorHeightFromRatio(0.01, 800)).toBe(144);
|
||||
expect(resolveQueryEditorEditorHeightFromRatio(0.5, 0)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT);
|
||||
});
|
||||
|
||||
it('clamps editor height to valid split bounds', () => {
|
||||
expect(clampQueryEditorEditorHeight(20, 800)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT);
|
||||
expect(clampQueryEditorEditorHeight(760, 800)).toBe(800 - MIN_QUERY_EDITOR_RESULT_HEIGHT);
|
||||
expect(clampQueryEditorEditorHeight(360, 800)).toBe(360);
|
||||
});
|
||||
|
||||
it('resolves persisted ratio from the final editor height', () => {
|
||||
expect(resolveQueryEditorEditorHeightRatio(480, 800)).toBe(0.6);
|
||||
expect(resolveQueryEditorEditorHeightRatio(20, 800)).toBe(MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
expect(resolveQueryEditorEditorHeightRatio(760, 800)).toBe(MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
expect(resolveQueryEditorEditorHeightRatio(480, 0)).toBe(DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO);
|
||||
});
|
||||
});
|
||||
66
frontend/src/utils/queryEditorSplitLayout.ts
Normal file
66
frontend/src/utils/queryEditorSplitLayout.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
export const DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO = 0.5;
|
||||
export const MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO = 0.18;
|
||||
export const MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO = 0.85;
|
||||
export const MIN_QUERY_EDITOR_EDITOR_HEIGHT = 100;
|
||||
export const MIN_QUERY_EDITOR_RESULT_HEIGHT = 120;
|
||||
|
||||
export const sanitizeQueryEditorEditorHeightRatio = (value: unknown): number => {
|
||||
const ratio = Number(value);
|
||||
if (!Number.isFinite(ratio)) {
|
||||
return DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO;
|
||||
}
|
||||
return Math.min(
|
||||
MAX_QUERY_EDITOR_EDITOR_HEIGHT_RATIO,
|
||||
Math.max(MIN_QUERY_EDITOR_EDITOR_HEIGHT_RATIO, ratio),
|
||||
);
|
||||
};
|
||||
|
||||
export const clampQueryEditorEditorHeight = (
|
||||
height: unknown,
|
||||
availableHeight: unknown,
|
||||
): number => {
|
||||
const rawHeight = Number(height);
|
||||
const rawAvailableHeight = Number(availableHeight);
|
||||
const normalizedHeight = Number.isFinite(rawHeight) ? rawHeight : MIN_QUERY_EDITOR_EDITOR_HEIGHT;
|
||||
if (!Number.isFinite(rawAvailableHeight) || rawAvailableHeight <= 0) {
|
||||
return Math.max(MIN_QUERY_EDITOR_EDITOR_HEIGHT, Math.round(normalizedHeight));
|
||||
}
|
||||
const maxEditorHeight = Math.max(
|
||||
MIN_QUERY_EDITOR_EDITOR_HEIGHT,
|
||||
rawAvailableHeight - MIN_QUERY_EDITOR_RESULT_HEIGHT,
|
||||
);
|
||||
return Math.round(Math.max(
|
||||
MIN_QUERY_EDITOR_EDITOR_HEIGHT,
|
||||
Math.min(maxEditorHeight, normalizedHeight),
|
||||
));
|
||||
};
|
||||
|
||||
export const resolveQueryEditorEditorHeightFromRatio = (
|
||||
ratio: unknown,
|
||||
availableHeight: unknown,
|
||||
): number => {
|
||||
const rawAvailableHeight = Number(availableHeight);
|
||||
if (!Number.isFinite(rawAvailableHeight) || rawAvailableHeight <= 0) {
|
||||
return MIN_QUERY_EDITOR_EDITOR_HEIGHT;
|
||||
}
|
||||
return clampQueryEditorEditorHeight(
|
||||
rawAvailableHeight * sanitizeQueryEditorEditorHeightRatio(ratio),
|
||||
rawAvailableHeight,
|
||||
);
|
||||
};
|
||||
|
||||
export const resolveQueryEditorEditorHeightRatio = (
|
||||
editorHeight: unknown,
|
||||
availableHeight: unknown,
|
||||
): number => {
|
||||
const rawEditorHeight = Number(editorHeight);
|
||||
const rawAvailableHeight = Number(availableHeight);
|
||||
if (
|
||||
!Number.isFinite(rawEditorHeight)
|
||||
|| !Number.isFinite(rawAvailableHeight)
|
||||
|| rawAvailableHeight <= 0
|
||||
) {
|
||||
return DEFAULT_QUERY_EDITOR_EDITOR_HEIGHT_RATIO;
|
||||
}
|
||||
return sanitizeQueryEditorEditorHeightRatio(rawEditorHeight / rawAvailableHeight);
|
||||
};
|
||||
Reference in New Issue
Block a user