4.6.1#619
Conversation
…e-path feat: 接入编辑器预览同步协议 V1 核心链路
# Conflicts: # packages/origine2/src/pages/editor/TextEditor/TextEditor.tsx
Memoize debounced submit and add cleanup
添加了韩文翻译
- Specify main contributor of Korean translation - Fix Korean typo: change '커그텀' to '커스텀'
fix: update Korean translations and contributor info
add kor translation
feat: virtual list for graphical editor
Fix drag adjustment
There was a problem hiding this comment.
Code Review
This pull request introduces a new @webgal/editor-preview-protocol package and refactors the editor-preview communication to use a structured client-host model. It also adds Korean language support, introduces a debug variables panel, optimizes graphical editor performance in long scenes using virtualized rendering, and adds several new graphical editing options. The code review feedback highlights several important improvements: adding guard checks to prevent type errors when target is undefined, securing postMessage target origins to prevent sensitive data leakage, robustly handling typed arrays during raw message normalization, avoiding React uncontrolled input warnings by providing fallbacks for unlockOrder.value, and replacing risky non-null assertions with safe fallbacks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| export async function SetFtoChangeF(target: string, scenePath: string, lineNumber: number): Promise<string> { | ||
| const sceneTXT = await GetSceneTXT(scenePath); | ||
| const lines = sceneTXT.split('\n').slice(0, lineNumber - 1).reverse(); | ||
| const direction = target.replace('fig-', ''); |
There was a problem hiding this comment.
If target is undefined (e.g., when -target is missing in setTransform), calling target.replace('fig-', '') will throw a TypeError. It is safer to add a guard check at the beginning of SetFtoChangeF.
export async function SetFtoChangeF(target: string, scenePath: string, lineNumber: number): Promise<string> {
if (!target) throw new Error('Target is undefined');
const sceneTXT = await GetSceneTXT(scenePath);
const lines = sceneTXT.split('\\n').slice(0, lineNumber - 1).reverse();
const direction = target.replace('fig-', '');| } | ||
|
|
||
| ifRef.current.onload = () => setTimeout(() => WsUtil.sendFontOptimizationCommand(isUseFontOptimization), 1000); | ||
| iframeWindow.postMessage(createPreviewBootstrapProvide(embeddedLaunchIdRef.current), '*'); |
There was a problem hiding this comment.
Using * as the target origin in postMessage allows any origin to receive the message if the iframe is redirected. It is safer to specify the target origin (e.g., window.location.origin) instead of * to prevent sensitive data (like embeddedLaunchId) from leaking.
| iframeWindow.postMessage(createPreviewBootstrapProvide(embeddedLaunchIdRef.current), '*'); | |
| iframeWindow.postMessage(createPreviewBootstrapProvide(embeddedLaunchIdRef.current), window.location.origin); |
| private normalizeRawMessage(rawData: RawData): string { | ||
| if (typeof rawData === 'string') { | ||
| return rawData; | ||
| } | ||
|
|
||
| if (Array.isArray(rawData)) { | ||
| return Buffer.concat(rawData).toString(); | ||
| } | ||
|
|
||
| if (rawData instanceof ArrayBuffer) { | ||
| return Buffer.from(rawData).toString(); | ||
| } | ||
|
|
||
| return rawData.toString(); | ||
| } |
There was a problem hiding this comment.
If rawData is a typed array (like Uint8Array but not a Buffer), rawData.toString() will return a comma-separated string of numbers (e.g., "104,101,108,108,111") instead of the decoded string. Explicitly checking Buffer.isBuffer(rawData) and handling ArrayBuffer.isView(rawData) makes the decoding much more robust.
private normalizeRawMessage(rawData: RawData): string {
if (typeof rawData === 'string') {
return rawData;
}
if (Buffer.isBuffer(rawData)) {
return rawData.toString();
}
if (Array.isArray(rawData)) {
return Buffer.concat(rawData).toString();
}
if (rawData instanceof ArrayBuffer) {
return Buffer.from(rawData).toString();
}
if (ArrayBuffer.isView(rawData)) {
return Buffer.from(rawData.buffer, rawData.byteOffset, rawData.byteLength).toString();
}
return rawData.toString();
}| {!isNoFile && <CommonOptions key="3.2" title={t`鉴赏排序`}> | ||
| <input | ||
| type="number" | ||
| value={unlockOrder.value} |
There was a problem hiding this comment.
If unlockOrder.value is undefined (when order is not specified), passing it directly to value of an <input> makes it uncontrolled, which can trigger a React warning ("A component is changing an uncontrolled input to be controlled") when it later becomes defined. Falling back to "" prevents this.
| value={unlockOrder.value} | |
| value={unlockOrder.value ?? ""} |
| {unlockType.value === "unlockCg" && <CommonOptions title={t`鉴赏排序`}> | ||
| <input | ||
| type="number" | ||
| value={unlockOrder.value} |
There was a problem hiding this comment.
If unlockOrder.value is undefined (when order is not specified), passing it directly to value of an <input> makes it uncontrolled, which can trigger a React warning ("A component is changing an uncontrolled input to be controlled") when it later becomes defined. Falling back to "" prevents this.
| value={unlockOrder.value} | |
| value={unlockOrder.value ?? ""} |
| setPanel({ ...panel, options: { ...panel.options, [key]: value } }); | ||
| eventBus.emit('editor:global-effect-editor-event', { editorId: panel.editorId, action: 'option', key, value, submit }); | ||
| }; | ||
| const options = panel.options!; |
There was a problem hiding this comment.
No description provided.