Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
762 changes: 50 additions & 712 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ allowBuilds:
overrides:
tar-fs@2: 2.1.4
node-forge: '>=1.3.2'
'@codemirror/state': 6.5.4
# langium is pulled transitively via @ant-design/x and @lobehub/ui →
# mermaid → @mermaid-js/parser. 4.2.1–4.2.3 import vscode-languageserver-
# {types,protocol}, vscode-jsonrpc and @chevrotain/regexp-to-ast without
Expand Down
5 changes: 0 additions & 5 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
"@ant-design/icons": "catalog:",
"@ant-design/x": "^2.5.0",
"@cloudscape-design/board-components": "3.0.60",
"@codemirror/lang-jinja": "^6.0.1",
"@codemirror/lang-liquid": "^6.3.2",
"@codemirror/language": "^6.12.3",
"@dicebear/collection": "^9.4.2",
"@dicebear/core": "^9.4.2",
"@lobehub/fluent-emoji": "^2.0.0",
Expand All @@ -24,8 +21,6 @@
"@react-hook/resize-observer": "^2.0.2",
"@tanstack/react-query": "catalog:",
"@testing-library/react": "catalog:",
"@uiw/codemirror-extensions-langs": "^4.25.9",
"@uiw/react-codemirror": "^4.25.9",
"ahooks": "catalog:",
"ai": "^5.0.173",
"ajv": "^8.18.0",
Expand Down
95 changes: 72 additions & 23 deletions react/src/components/BAICodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,42 @@
@license
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import { loadMonacoEditor } from '../helper/monacoEditor';
import useControllableState_deprecated from '../hooks/useControllableState';
import { useThemeMode } from '../hooks/useThemeMode';
import { loadLanguage, LanguageName } from '@uiw/codemirror-extensions-langs';
import CodeMirror, {
ReactCodeMirrorProps,
EditorView,
} from '@uiw/react-codemirror';
import type { EditorProps } from '@monaco-editor/react';
import { Skeleton, theme } from 'antd';
import React, { Suspense } from 'react';

interface BAICodeEditorProps extends Omit<ReactCodeMirrorProps, 'language'> {
const MonacoEditor: React.LazyExoticComponent<React.FC<EditorProps>> =
React.lazy(() =>
loadMonacoEditor().then((module) => ({
default: module.Editor as React.FC<EditorProps>,
})),
);

// Language alias preserved from the previous codemirror-based API so existing
// call sites keep working. Extend as needed when adding new languages.
export type BAICodeEditorLanguage = 'json' | 'sh' | 'yaml' | 'toml';

const MONACO_LANGUAGE_MAP: Record<BAICodeEditorLanguage, string> = {
json: 'json',
sh: 'shell',
yaml: 'yaml',
toml: 'plaintext',
};

interface BAICodeEditorProps extends Omit<
EditorProps,
'language' | 'value' | 'onChange'
> {
value?: string;
onChange?: (value: string) => void;
language?: LanguageName;
language?: BAICodeEditorLanguage;
editable?: boolean;
showLineNumbers?: boolean;
lineWrapping?: boolean;
style?: React.CSSProperties;
}

const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
Expand All @@ -26,34 +47,62 @@ const BAICodeEditor: React.FC<BAICodeEditorProps> = ({
editable = false,
showLineNumbers = true,
lineWrapping = false,
...CodeMirrorProps
height = 200,
style,
options,
...editorProps
}) => {
'use memo';
const { isDarkMode } = useThemeMode();
const { token } = theme.useToken();

const [script, setScript] = useControllableState_deprecated<string>({
defaultValue: '',
value,
onChange,
});
const languageExtension = loadLanguage(language);
const extensions = languageExtension ? [languageExtension] : [];

return (
<CodeMirror
theme={isDarkMode ? 'dark' : 'light'}
extensions={
lineWrapping ? [EditorView.lineWrapping, ...extensions] : extensions
}
editable={editable}
readOnly={!editable}
basicSetup={{
lineNumbers: showLineNumbers,
const loadingFallback = (
<Skeleton
active
style={{
paddingInline: token.paddingContentHorizontal,
paddingBlock: token.paddingContentVertical,
}}
value={script}
onChange={(value) => setScript(value)}
{...CodeMirrorProps}
/>
);

return (
<div
style={{
border: `1px solid ${token.colorBorder}`,
borderRadius: token.borderRadius,
overflow: 'hidden',
...style,
}}
>
<Suspense fallback={loadingFallback}>
<MonacoEditor
language={MONACO_LANGUAGE_MAP[language]}
height={height}
theme={isDarkMode ? 'vs-dark' : 'vs'}
value={script}
onChange={(v: string | undefined) => setScript(v ?? '')}
loading={loadingFallback}
options={{
readOnly: !editable,
lineNumbers: showLineNumbers ? 'on' : 'off',
wordWrap: lineWrapping ? 'on' : 'off',
minimap: { enabled: false },
scrollBeyondLastLine: false,
fixedOverflowWidgets: true,
...options,
}}
{...editorProps}
/>
</Suspense>
</div>
);
};

export default BAICodeEditor;
3 changes: 0 additions & 3 deletions react/src/components/BAIJSONViewerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
@license
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import { useThemeMode } from '../hooks/useThemeMode';
import BAICodeEditor from './BAICodeEditor';
import { Alert } from 'antd';
import { BAIFlex, BAIModal, BAIModalProps } from 'backend.ai-ui';
Expand All @@ -20,7 +19,6 @@ const BAIJSONViewerModal: React.FC<BAIJSONViewerModalProps> = ({
...modalProps
}) => {
const { t } = useTranslation();
const { isDarkMode } = useThemeMode();

const { formattedJson, hasError } = useMemo(() => {
if (typeof json === 'string') {
Expand Down Expand Up @@ -56,7 +54,6 @@ const BAIJSONViewerModal: React.FC<BAIJSONViewerModalProps> = ({
<BAICodeEditor
value={formattedJson}
language={'json'}
theme={isDarkMode ? 'dark' : 'light'}
editable={false}
/>
</BAIFlex>
Expand Down
3 changes: 0 additions & 3 deletions react/src/components/ContainerRegistryEditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ContainerRegistryEditorModalCreateMutation } from '../__generated__/Con
import { ContainerRegistryEditorModalFragment$key } from '../__generated__/ContainerRegistryEditorModalFragment.graphql';
import { ContainerRegistryEditorModalModifyRegistryMutation } from '../__generated__/ContainerRegistryEditorModalModifyRegistryMutation.graphql';
import { useSuspendedBackendaiClient } from '../hooks';
import { useThemeMode } from '../hooks/useThemeMode';
import BAICodeEditor from './BAICodeEditor';
import HiddenFormItem from './HiddenFormItem';
import ProjectSelectForAdminPage from './ProjectSelectForAdminPage';
Expand Down Expand Up @@ -52,7 +51,6 @@ const ContainerRegistryEditorModal: React.FC<
> = ({ containerRegistryFrgmt = null, onOk, ...modalProps }) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const { isDarkMode } = useThemeMode();
const { message, modal } = App.useApp();

const baiClient = useSuspendedBackendaiClient();
Expand Down Expand Up @@ -544,7 +542,6 @@ const ContainerRegistryEditorModal: React.FC<
<BAICodeEditor
editable
language="json"
theme={isDarkMode ? 'dark' : 'light'}
style={{ width: '100%' }}
/>
</Form.Item>
Expand Down
22 changes: 21 additions & 1 deletion react/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,27 @@ export default defineConfig(({ mode }) => {
nodePolyfills({
include: ['buffer', 'stream'],
globals: {
Buffer: true,
// Why not `Buffer: true` in dev:
// With `true`, the plugin prepends
// `import __buffer_polyfill from 'vite-plugin-node-polyfills/shims/buffer';`
// `globalThis.Buffer = globalThis.Buffer || __buffer_polyfill;`
// to every chunk that touches Buffer. Vite's dep optimizer also
// wraps that same shim into a CJS-interop chunk — so the chunk
// that *exports* `__vite__cjsImport0_vitePluginNodePolyfills_
// shims_buffer` also *imports* it (via the injected prelude),
// and the import lands in the same chunk before the export is
// initialized → TDZ on first browser load. `'build'` scopes
// the injection to the production rollup build only.
//
// Why this is safe to do here:
// No app code under `react/src` or `packages/backend.ai-ui/src`
// references the Buffer global. The only Buffer.* call that
// survives into the prod bundle is `Buffer.byteLength` inside
// `cross-fetch/dist/browser-ponyfill.js` (pulled transitively
// by `i18next-http-backend`), and that lives on a Node-only
// code path the browser ponyfill never enters. The polyfill
// itself could likely be removed entirely; see follow-up.
Buffer: 'build',
global: false,
process: false,
},
Expand Down
Loading