Claude güncellemesi DynamicAppService değişiklikleri
This commit is contained in:
parent
b4c8fc3a2b
commit
c2f1b203ab
2 changed files with 221 additions and 131 deletions
|
|
@ -62,13 +62,6 @@ export interface DynamicAppServiceListResult {
|
|||
totalCount: number
|
||||
}
|
||||
|
||||
export const postTestCompile = (input: TestCompileDto) =>
|
||||
apiService.fetchData<CompileResult>({
|
||||
method: 'POST',
|
||||
url: `/api/app/dynamic-app-service/test-compile`,
|
||||
data: input as any,
|
||||
})
|
||||
|
||||
class DynamicServiceService {
|
||||
private readonly baseUrl = '/api/app/dynamic-app-service'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom'
|
||||
import { Editor } from '@monaco-editor/react'
|
||||
import type { editor } from 'monaco-editor'
|
||||
import {
|
||||
FaPlay,
|
||||
FaCopy,
|
||||
|
|
@ -17,14 +18,14 @@ import {
|
|||
dynamicServiceService,
|
||||
type CompileResult,
|
||||
type PublishResult,
|
||||
postTestCompile,
|
||||
type TestCompileDto,
|
||||
} from '@/services/dynamicService.service'
|
||||
import PageTitle from '@/components/shared/PageTitle'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import Button from '@/components/ui/Button'
|
||||
import { Notification, toast } from '@/components/ui'
|
||||
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||
|
||||
const defaultTemplate = `using System;
|
||||
const DEFAULT_TEMPLATE = `using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
|
@ -43,22 +44,52 @@ namespace DynamicServices
|
|||
|
||||
public virtual async Task<List<string>> GetSampleDataAsync()
|
||||
{
|
||||
return await Task.FromResult(new List<string>
|
||||
{
|
||||
"Item 1",
|
||||
"Item 2",
|
||||
"Item 3"
|
||||
return await Task.FromResult(new List<string>
|
||||
{
|
||||
"Item 1",
|
||||
"Item 2",
|
||||
"Item 3"
|
||||
});
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
const EDITOR_OPTIONS: editor.IStandaloneEditorConstructionOptions = {
|
||||
fontSize: 14,
|
||||
lineNumbers: 'on',
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: false },
|
||||
folding: true,
|
||||
wordWrap: 'on',
|
||||
}
|
||||
|
||||
const INPUT_BASE_CLASS =
|
||||
'w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100'
|
||||
const INPUT_IDLE_BORDER = 'border-slate-300 dark:border-gray-700'
|
||||
const INPUT_ERROR_BORDER = 'border-red-500 bg-red-50 dark:bg-red-900/20'
|
||||
const LABEL_CLASS =
|
||||
'block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1'
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string): string => {
|
||||
const response = (error as { response?: { data?: { error?: { message?: string }; message?: string } } })
|
||||
?.response
|
||||
return (
|
||||
response?.data?.error?.message ||
|
||||
response?.data?.message ||
|
||||
(error instanceof Error ? error.message : undefined) ||
|
||||
fallback
|
||||
)
|
||||
}
|
||||
|
||||
const DynamicServiceEditor: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const navigate = useNavigate()
|
||||
const { translate } = useLocalization()
|
||||
const { mode } = useConfig()
|
||||
|
||||
const [code, setCode] = useState(defaultTemplate)
|
||||
const [code, setCode] = useState(DEFAULT_TEMPLATE)
|
||||
const [serviceName, setServiceName] = useState('')
|
||||
const [displayName, setDisplayName] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
|
|
@ -68,120 +99,177 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
|
||||
const [isCompiling, setIsCompiling] = useState(false)
|
||||
const [isPublishing, setIsPublishing] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(Boolean(id))
|
||||
|
||||
const [compileResult, setCompileResult] = useState<CompileResult | null>(null)
|
||||
const [publishResult, setPublishResult] = useState<PublishResult | null>(null)
|
||||
|
||||
const editorOptions = {
|
||||
fontSize: 14,
|
||||
lineNumbers: 'on' as const,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: false },
|
||||
folding: true,
|
||||
wordWrap: 'on' as const,
|
||||
}
|
||||
const pushError = useCallback((message: string) => {
|
||||
toast.push(
|
||||
<Notification type="danger" duration={3000}>
|
||||
{message}
|
||||
</Notification>,
|
||||
)
|
||||
}, [])
|
||||
|
||||
const pushSuccess = useCallback((message: string) => {
|
||||
toast.push(
|
||||
<Notification type="success" duration={2000}>
|
||||
{message}
|
||||
</Notification>,
|
||||
)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
loadService(id)
|
||||
}
|
||||
}, [id])
|
||||
|
||||
const loadService = async (serviceId: string) => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const data = await dynamicServiceService.getById(serviceId)
|
||||
setCode(data.code)
|
||||
setServiceName(data.name)
|
||||
setDisplayName(data.displayName || '')
|
||||
setDescription(data.description || '')
|
||||
setPrimaryEntityType(data.primaryEntityType || '')
|
||||
setIsActive(data.isActive ?? true)
|
||||
} catch (error) {
|
||||
console.error('Servis yüklenirken hata:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleTestCompile = async () => {
|
||||
if (!code.trim()) {
|
||||
alert(translate('::App.DynamicServicesEditor.PleaseEnterCode'))
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
let cancelled = false
|
||||
|
||||
const loadService = async () => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const data = await dynamicServiceService.getById(id)
|
||||
if (cancelled) return
|
||||
setCode(data.code || DEFAULT_TEMPLATE)
|
||||
setServiceName(data.name)
|
||||
setDisplayName(data.displayName || '')
|
||||
setDescription(data.description || '')
|
||||
setPrimaryEntityType(data.primaryEntityType || '')
|
||||
setIsActive(data.isActive ?? true)
|
||||
} catch (error) {
|
||||
if (cancelled) return
|
||||
pushError(getErrorMessage(error, translate('::App.Platform.ErrorLoading')))
|
||||
} finally {
|
||||
if (!cancelled) setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadService()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [id, pushError, translate])
|
||||
|
||||
const runCompile = useCallback(async (): Promise<CompileResult> => {
|
||||
setIsCompiling(true)
|
||||
setCompileResult(null)
|
||||
try {
|
||||
setIsCompiling(true)
|
||||
setCompileResult(null)
|
||||
const result = await postTestCompile({ code } as TestCompileDto)
|
||||
setCompileResult(result.data)
|
||||
} catch (error: any) {
|
||||
setCompileResult({
|
||||
const result = await dynamicServiceService.testCompile({ code })
|
||||
setCompileResult(result)
|
||||
return result
|
||||
} catch (error) {
|
||||
const failed: CompileResult = {
|
||||
success: false,
|
||||
errorMessage:
|
||||
error.response?.data?.message ||
|
||||
errorMessage: getErrorMessage(
|
||||
error,
|
||||
translate('::App.DynamicServicesEditor.CompileError'),
|
||||
),
|
||||
compilationTimeMs: 0,
|
||||
hasWarnings: false,
|
||||
errors: [],
|
||||
})
|
||||
}
|
||||
setCompileResult(failed)
|
||||
return failed
|
||||
} finally {
|
||||
setIsCompiling(false)
|
||||
}
|
||||
}
|
||||
}, [code, translate])
|
||||
|
||||
const handlePublish = async () => {
|
||||
setSubmitted(true)
|
||||
if (!code.trim() || !serviceName.trim()) {
|
||||
const handleTestCompile = useCallback(async () => {
|
||||
if (isCompiling || isPublishing) return
|
||||
if (!code.trim()) {
|
||||
pushError(translate('::App.DynamicServicesEditor.PleaseEnterCode'))
|
||||
return
|
||||
}
|
||||
if (isPublishing) return
|
||||
try {
|
||||
setIsPublishing(true)
|
||||
setPublishResult(null)
|
||||
await runCompile()
|
||||
}, [code, isCompiling, isPublishing, pushError, runCompile, translate])
|
||||
|
||||
// Edit modunda: önce eskiyi sil, sonra yeniden yayınla
|
||||
const handlePublish = useCallback(async () => {
|
||||
if (isPublishing || isCompiling) return
|
||||
|
||||
setSubmitted(true)
|
||||
const name = serviceName.trim()
|
||||
if (!code.trim() || !name) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsPublishing(true)
|
||||
setPublishResult(null)
|
||||
try {
|
||||
// Edit modunda eski servis silinip yeniden yayınlanır; silmeden önce
|
||||
// kodun derlendiğinden emin ol ki başarısız derlemede servis kaybolmasın.
|
||||
if (id) {
|
||||
const compiled = await runCompile()
|
||||
if (!compiled.success) {
|
||||
return
|
||||
}
|
||||
await dynamicServiceService.delete(id)
|
||||
}
|
||||
|
||||
const result = await dynamicServiceService.publish({
|
||||
name: serviceName,
|
||||
name,
|
||||
code,
|
||||
displayName,
|
||||
description,
|
||||
primaryEntityType,
|
||||
displayName: displayName.trim(),
|
||||
description: description.trim(),
|
||||
primaryEntityType: primaryEntityType.trim(),
|
||||
isActive,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
pushSuccess(translate('::App.Platform.SavedSuccessfully'))
|
||||
navigate(ROUTES_ENUM.protected.saas.developerKit.dynamicServices)
|
||||
} else {
|
||||
setPublishResult(result)
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
setPublishResult({
|
||||
success: false,
|
||||
errorMessage:
|
||||
error.response?.data?.message ||
|
||||
errorMessage: getErrorMessage(
|
||||
error,
|
||||
translate('::App.DynamicServicesEditor.PublishError'),
|
||||
),
|
||||
})
|
||||
} finally {
|
||||
setIsPublishing(false)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
code,
|
||||
description,
|
||||
displayName,
|
||||
id,
|
||||
isActive,
|
||||
isCompiling,
|
||||
isPublishing,
|
||||
navigate,
|
||||
primaryEntityType,
|
||||
pushSuccess,
|
||||
runCompile,
|
||||
serviceName,
|
||||
translate,
|
||||
])
|
||||
|
||||
const copyCode = () => {
|
||||
navigator.clipboard.writeText(code)
|
||||
alert(translate('::App.DynamicServicesEditor.CodeCopied'))
|
||||
}
|
||||
const copyCode = useCallback(async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code)
|
||||
pushSuccess(translate('::App.DynamicServicesEditor.CodeCopied'))
|
||||
} catch (error) {
|
||||
pushError(getErrorMessage(error, translate('::App.Platform.Error')))
|
||||
}
|
||||
}, [code, pushError, pushSuccess, translate])
|
||||
|
||||
const pageTitle = id
|
||||
? translate('::App.DynamicServicesEditor.EditTitle')
|
||||
: translate('::App.DeveloperKitDynamicServices.NewService')
|
||||
|
||||
const { lineCount, charCount } = useMemo(
|
||||
() => ({ lineCount: code.split('\n').length, charCount: code.length }),
|
||||
[code],
|
||||
)
|
||||
|
||||
const serviceNameError = submitted && !serviceName.trim()
|
||||
const isBusy = isCompiling || isPublishing
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
|
|
@ -190,8 +278,6 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const serviceNameError = submitted && !serviceName.trim()
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<PageTitle title={pageTitle} />
|
||||
|
|
@ -216,16 +302,16 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: action buttons + swagger + publish */}
|
||||
{/* Right: action buttons */}
|
||||
<div className="flex items-center gap-2 pr-3">
|
||||
<Link
|
||||
to={ROUTES_ENUM.protected.saas.developerKit.dynamicServices}
|
||||
className="flex items-center gap-2 text-slate-600 dark:text-gray-300 text-black dark:text-white px-4 py-2 rounded-lg hover:text-slate-700 dark:hover:text-gray-100 transition-colors"
|
||||
className="flex items-center gap-2 text-slate-600 dark:text-gray-300 px-4 py-2 rounded-lg hover:text-slate-800 dark:hover:text-gray-100 transition-colors"
|
||||
>
|
||||
<FaArrowLeft className="w-3.5 h-3.5" />
|
||||
{translate('::App.DynamicServicesEditor.BackToServices')}
|
||||
</Link>
|
||||
<div className="h-6 w-px bg-slate-300 dark:bg-gray-700"></div>
|
||||
<div className="h-6 w-px bg-slate-300 dark:bg-gray-700" />
|
||||
<Button
|
||||
type="button"
|
||||
onClick={copyCode}
|
||||
|
|
@ -238,7 +324,7 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
<Button
|
||||
type="button"
|
||||
onClick={handleTestCompile}
|
||||
disabled={isCompiling || !code.trim()}
|
||||
disabled={isBusy || !code.trim()}
|
||||
variant="solid"
|
||||
icon={
|
||||
isCompiling ? (
|
||||
|
|
@ -256,7 +342,7 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
<Button
|
||||
type="button"
|
||||
onClick={handlePublish}
|
||||
disabled={isPublishing}
|
||||
disabled={isBusy || !code.trim()}
|
||||
variant="solid"
|
||||
color="green-500"
|
||||
icon={
|
||||
|
|
@ -295,15 +381,22 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
? translate('::App.DynamicServicesEditor.CompileSuccess')
|
||||
: translate('::App.DynamicServicesEditor.CompileFailed')}
|
||||
</span>
|
||||
{!compileResult.success && compileResult.errors && compileResult.errors.length > 0 && (
|
||||
<ul className="mt-1 space-y-0.5">
|
||||
{compileResult.errors.map((e, i) => (
|
||||
<li key={i} className="text-xs font-mono">
|
||||
[{e.code}] {translate('::App.DynamicServicesEditor.Line')}{' '}
|
||||
{e.line}: {e.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{!compileResult.success && (
|
||||
<>
|
||||
{compileResult.errorMessage && !compileResult.errors?.length && (
|
||||
<p className="text-xs mt-0.5">{compileResult.errorMessage}</p>
|
||||
)}
|
||||
{!!compileResult.errors?.length && (
|
||||
<ul className="mt-1 space-y-0.5">
|
||||
{compileResult.errors.map((e, i) => (
|
||||
<li key={`${e.code}-${e.line}-${i}`} className="text-xs font-mono">
|
||||
[{e.code}] {translate('::App.DynamicServicesEditor.Line')}{' '}
|
||||
{e.line}: {e.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-slate-400 shrink-0">
|
||||
|
|
@ -330,7 +423,6 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
<div className="flex flex-col xl:flex-row gap-4 items-stretch xl:items-start">
|
||||
{/* LEFT PANEL — Servis Ayarları */}
|
||||
<div className="w-full xl:w-1/4 shrink-0 bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 p-5 space-y-4">
|
||||
{/* Panel header */}
|
||||
<div className="flex items-center gap-2 pb-3 border-b border-slate-100 dark:border-gray-700">
|
||||
<FaCog className="w-4 h-4 text-blue-500" />
|
||||
<h2 className="font-semibold text-slate-700 dark:text-gray-100 text-sm">
|
||||
|
|
@ -340,24 +432,21 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
|
||||
{/* Servis Adı */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||
<label className={LABEL_CLASS} htmlFor="dse-service-name">
|
||||
{translate('::App.DynamicServicesEditor.ServiceName')}
|
||||
</label>
|
||||
<input
|
||||
id="dse-service-name"
|
||||
type="text"
|
||||
value={serviceName}
|
||||
onChange={(e) => {
|
||||
setServiceName(e.target.value)
|
||||
setSubmitted(false)
|
||||
}}
|
||||
onChange={(e) => setServiceName(e.target.value)}
|
||||
placeholder={translate(
|
||||
'::App.DynamicServicesEditor.ServiceNamePlaceholder',
|
||||
)}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 ${
|
||||
serviceNameError
|
||||
? 'border-red-500 bg-red-50 dark:bg-red-900/20'
|
||||
: 'border-slate-300 dark:border-gray-700'
|
||||
className={`${INPUT_BASE_CLASS} ${
|
||||
serviceNameError ? INPUT_ERROR_BORDER : INPUT_IDLE_BORDER
|
||||
}`}
|
||||
aria-invalid={serviceNameError}
|
||||
autoFocus
|
||||
/>
|
||||
{serviceNameError && (
|
||||
|
|
@ -369,70 +458,76 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
|
||||
{/* Görünen Ad */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||
<label className={LABEL_CLASS} htmlFor="dse-display-name">
|
||||
{translate('::App.Platform.DisplayName')}
|
||||
</label>
|
||||
<input
|
||||
id="dse-display-name"
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder={translate(
|
||||
'::App.DynamicServicesEditor.DisplayNamePlaceholder',
|
||||
)}
|
||||
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||
className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Açıklama */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||
<label className={LABEL_CLASS} htmlFor="dse-description">
|
||||
{translate('::App.Listform.ListformField.Description')}
|
||||
</label>
|
||||
<input
|
||||
id="dse-description"
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={translate(
|
||||
'::App.DynamicServicesEditor.DescriptionPlaceholder',
|
||||
)}
|
||||
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||
className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Ana Entity Türü */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-gray-300 mb-1">
|
||||
<label className={LABEL_CLASS} htmlFor="dse-primary-entity">
|
||||
{translate('::App.DynamicServicesEditor.PrimaryEntityType')}
|
||||
</label>
|
||||
<input
|
||||
id="dse-primary-entity"
|
||||
type="text"
|
||||
value={primaryEntityType}
|
||||
onChange={(e) => setPrimaryEntityType(e.target.value)}
|
||||
placeholder={translate(
|
||||
'::App.DynamicServicesEditor.PrimaryEntityTypePlaceholder',
|
||||
)}
|
||||
className="w-full px-3 py-2 border border-slate-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||
className={`${INPUT_BASE_CLASS} ${INPUT_IDLE_BORDER}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Aktif */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
{translate('::App.Listform.ListformField.IsActive')}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
id="dse-is-active"
|
||||
type="checkbox"
|
||||
checked={isActive}
|
||||
onChange={(e) => setIsActive(e.target.checked)}
|
||||
className="w-4 h-4 rounded accent-blue-600 cursor-pointer"
|
||||
/>
|
||||
<label
|
||||
className="text-sm font-medium text-slate-700 dark:text-gray-300 cursor-pointer"
|
||||
htmlFor="dse-is-active"
|
||||
>
|
||||
{translate('::App.Listform.ListformField.IsActive')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* RIGHT PANEL — Önizleme + Editor */}
|
||||
<div className="w-full flex-1 min-w-0 space-y-4">
|
||||
{/* Monaco Editor */}
|
||||
<div className="bg-white rounded-lg border border-slate-200 overflow-hidden">
|
||||
{/* RIGHT PANEL — Editor */}
|
||||
<div className="w-full flex-1 min-w-0">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-lg border border-slate-200 dark:border-gray-700 overflow-hidden">
|
||||
<div className="px-5 py-3 bg-slate-50 dark:bg-gray-800 border-b border-slate-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaCode className="w-4 h-4 text-slate-500 dark:text-gray-400" />
|
||||
|
|
@ -442,22 +537,24 @@ const DynamicServiceEditor: React.FC = () => {
|
|||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-slate-500 dark:text-gray-400">
|
||||
<span>
|
||||
{translate('::App.DynamicServicesEditor.LineCount')}{' '}
|
||||
{code.split('\n').length}
|
||||
{translate('::App.DynamicServicesEditor.LineCount')} {lineCount}
|
||||
</span>
|
||||
<span className="text-slate-300">|</span>
|
||||
<span className="text-slate-300 dark:text-gray-600">|</span>
|
||||
<span>
|
||||
{translate('::App.DynamicServicesEditor.CharCount')} {code.length}
|
||||
{translate('::App.DynamicServicesEditor.CharCount')} {charCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ height: '560px' }}>
|
||||
<div className="h-[560px]">
|
||||
<Editor
|
||||
defaultLanguage="csharp"
|
||||
value={code}
|
||||
onChange={(value) => setCode(value || '')}
|
||||
options={editorOptions}
|
||||
theme="vs-dark"
|
||||
onChange={(value) => setCode(value ?? '')}
|
||||
options={EDITOR_OPTIONS}
|
||||
theme={mode === 'dark' ? 'vs-dark' : 'light'}
|
||||
loading={
|
||||
<FaSpinner className="w-6 h-6 animate-spin text-slate-400" />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue