Browse Source

fix advanced editor responsiveness and a bug-fix

imwald
Silberengel 4 weeks ago
parent
commit
7e344e75da
  1. 7
      electron/main.cjs
  2. 423
      src/components/AdvancedEventLab/AdvancedEventLabDialog.tsx

7
electron/main.cjs

@ -345,9 +345,10 @@ function createWindow() {
contextIsolation: true, contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
sandbox: true, sandbox: true,
// Packaged shell is loopback `http://127.0.0.1` → public HTTPS APIs; Chromium CORS blocks // Always on: CORS, CSP, and mixed-content protections. Loopback packaged shell still reaches
// renderer `fetch` unless disabled for this window (IPC + net.fetch still used as defense). // public APIs via `relaxCorsForRendererSubresources()` (media/HLS) and `imwald:backend-request`
webSecurity: !app.isPackaged // IPC (`electronAwareFetch` for translate + LanguageTool).
webSecurity: true
} }
}) })

423
src/components/AdvancedEventLab/AdvancedEventLabDialog.tsx

@ -3,7 +3,6 @@ import { Input } from '@/components/ui/input'
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle DialogTitle
} from '@/components/ui/dialog' } from '@/components/ui/dialog'
@ -644,7 +643,7 @@ export default function AdvancedEventLabDialog({
'.cm-scroller': { overflow: 'auto', minHeight: 0 }, '.cm-scroller': { overflow: 'auto', minHeight: 0 },
// Large dvh mins fight stacked flex/grid rows and overflow onto the preview; host + row cap height instead. // Large dvh mins fight stacked flex/grid rows and overflow onto the preview; host + row cap height instead.
'.cm-content': { '.cm-content': {
minHeight: '11rem', minHeight: '12rem',
fontFamily: 'var(--font-mono, ui-monospace, monospace)' fontFamily: 'var(--font-mono, ui-monospace, monospace)'
}, },
// LanguageTool hits: drop default thin SVG underline, use thick wavy line (see `LT_GRAMMAR_MARK_CLASS`). // LanguageTool hits: drop default thin SVG underline, use thick wavy line (see `LT_GRAMMAR_MARK_CLASS`).
@ -878,203 +877,30 @@ export default function AdvancedEventLabDialog({
<DialogTitle>{t('Advanced event lab')}</DialogTitle> <DialogTitle>{t('Advanced event lab')}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-2 px-4 py-2 border-b shrink-0 flex-wrap"> <div className="min-h-0 max-h-[calc(90dvh-3.25rem)] overflow-y-auto overscroll-y-contain">
<div className="flex flex-wrap items-end gap-3"> <div className="flex shrink-0 flex-col px-4 py-2 pb-4">
{isLanguageToolConfigured() ? ( <div className="mb-2 flex flex-wrap gap-2">
<div className="space-y-1 min-w-[10rem]"> <Button
<Label htmlFor="lt-lang">{t('Advanced lab grammar language')}</Label> type="button"
<Select variant="outline"
value={ltLang} size="sm"
onValueChange={(code) => { disabled={!canUndoCheckpoint}
logger.info('[AdvancedLab] grammar language changed', { from: ltLang, to: code }) title={t('Advanced lab undo checkpoint hint')}
setLtLang(code) onClick={handleUndoCheckpoint}
}} >
> <Undo2 className="mr-1 inline h-4 w-4" />
<SelectTrigger id="lt-lang" className="min-w-[220px] max-w-md w-auto"> {t('Advanced lab undo checkpoint')}
<SelectValue />
</SelectTrigger>
<SelectContent
className="max-h-64 min-w-[var(--radix-select-trigger-width)] p-0"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div
className="sticky top-0 z-10 border-b border-border bg-popover p-2"
onPointerDown={(e) => e.stopPropagation()}
>
<Input
type="search"
value={ltLangFilter}
onChange={(e) => setLtLangFilter(e.target.value)}
placeholder={t('Language list filter placeholder')}
className="h-8"
aria-label={t('Language list filter placeholder')}
/>
</div>
<div className="py-1">
{ltListFiltered.map((code) => (
<SelectItem
key={code}
value={code}
className="items-start py-2.5 whitespace-normal"
>
<LanguageSelectOptionLines tag={code} className="w-full" />
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
) : null}
{isTranslateConfigured() ? (
<div className="flex flex-col gap-2 min-w-0">
{translateLoad === 'idle' || translateLoad === 'loading' ? (
<p className="text-xs text-muted-foreground">{t('Advanced lab translation languages loading')}</p>
) : null}
{translateLoad === 'ready' ? (
<div className="flex flex-wrap items-end gap-3">
<div className="space-y-1 min-w-[10rem]">
<Label htmlFor="tr-src">{t('Advanced lab translation source')}</Label>
<Select
value={translateSource}
onValueChange={(v) => {
logger.info('[AdvancedLab] translation source language changed', {
from: translateSource,
to: v
})
setTranslateSource(v)
if (v !== 'auto' && v === translateTarget) {
const alt = translateLangs.find((l) => l.code !== v)?.code
if (alt) setTranslateTarget(alt)
}
}}
>
<SelectTrigger id="tr-src" className="min-w-[220px] max-w-md w-auto">
<SelectValue />
</SelectTrigger>
<SelectContent
className="max-h-64 min-w-[var(--radix-select-trigger-width)] p-0"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div
className="sticky top-0 z-10 border-b border-border bg-popover p-2"
onPointerDown={(e) => e.stopPropagation()}
>
<Input
type="search"
value={translateSrcFilter}
onChange={(e) => setTranslateSrcFilter(e.target.value)}
placeholder={t('Language list filter placeholder')}
className="h-8"
aria-label={t('Language list filter placeholder')}
/>
</div>
<div className="py-1">
{showTranslateSourceAuto ? (
<SelectItem value="auto">{t('Advanced lab translation source auto')}</SelectItem>
) : null}
{translateLangsFilteredSrc.map((l) => (
<SelectItem
key={l.code}
value={l.code}
className="items-start py-2.5 whitespace-normal"
>
<LanguageSelectOptionLines tag={l.code} className="w-full" />
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
<div className="space-y-1 min-w-[10rem]">
<Label htmlFor="tr-tgt">{t('Advanced lab translation target')}</Label>
<Select
value={translateTarget}
onValueChange={(v) => {
logger.info('[AdvancedLab] translation target language changed', {
from: translateTarget,
to: v
})
setTranslateTarget(v)
if (translateSource !== 'auto' && v === translateSource) {
setTranslateSource('auto')
}
}}
>
<SelectTrigger id="tr-tgt" className="min-w-[220px] max-w-md w-auto">
<SelectValue />
</SelectTrigger>
<SelectContent
className="max-h-64 min-w-[var(--radix-select-trigger-width)] p-0"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div
className="sticky top-0 z-10 border-b border-border bg-popover p-2"
onPointerDown={(e) => e.stopPropagation()}
>
<Input
type="search"
value={translateTgtFilter}
onChange={(e) => setTranslateTgtFilter(e.target.value)}
placeholder={t('Language list filter placeholder')}
className="h-8"
aria-label={t('Language list filter placeholder')}
/>
</div>
<div className="py-1">
{translateLangsFilteredTgt.map((l) => (
<SelectItem
key={l.code}
value={l.code}
className="items-start py-2.5 whitespace-normal"
>
<LanguageSelectOptionLines tag={l.code} className="w-full" />
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
<Button type="button" variant="secondary" size="sm" onClick={() => void handleTranslate()}>
{t('Advanced lab translate')}
</Button>
</div>
) : null}
{translateLoad === 'empty' ? (
<p className="text-xs text-destructive">{t('Advanced lab translation languages empty')}</p>
) : null}
{translateLoad === 'error' ? (
<p className="text-xs text-destructive">{t('Advanced lab translation languages error')}</p>
) : null}
</div>
) : null}
{contextEventId && isTranslateConfigured() ? (
<Button type="button" variant="outline" size="sm" onClick={handleReadAloudBuffer}>
{t('Advanced lab use translation read aloud')}
</Button> </Button>
) : null} </div>
<Button
type="button"
variant="outline"
size="sm"
disabled={!canUndoCheckpoint}
title={t('Advanced lab undo checkpoint hint')}
onClick={handleUndoCheckpoint}
>
<Undo2 className="h-4 w-4 mr-1 inline" />
{t('Advanced lab undo checkpoint')}
</Button>
</div>
</div>
<div className="flex min-h-0 flex-1 flex-col overflow-hidden overscroll-y-contain px-4 py-2"> <Tabs
<Tabs
value={labBodyTab} value={labBodyTab}
onValueChange={(v) => { onValueChange={(v) => {
const next = v as 'edit' | 'preview' const next = v as 'edit' | 'preview'
if (next === 'preview') flushPreviewDocNow() if (next === 'preview') flushPreviewDocNow()
setLabBodyTab(next) setLabBodyTab(next)
}} }}
className="flex min-h-0 flex-1 flex-col gap-2" className="flex flex-col gap-2"
> >
<TabsList className="h-auto w-auto shrink-0 flex-wrap justify-start gap-1 p-1"> <TabsList className="h-auto w-auto shrink-0 flex-wrap justify-start gap-1 p-1">
<TabsTrigger value="edit" className="shrink-0"> <TabsTrigger value="edit" className="shrink-0">
@ -1092,20 +918,20 @@ export default function AdvancedEventLabDialog({
<TabsContent <TabsContent
value="edit" value="edit"
forceMount forceMount
className="mt-0 flex min-h-0 flex-1 flex-col gap-2 overflow-hidden data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0" className="mt-0 flex flex-col gap-2 data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0"
> >
<AdvancedEventLabMarkupToolbar markupMode={markupMode} viewRef={markupView} sliceRef={sliceRef} /> <AdvancedEventLabMarkupToolbar markupMode={markupMode} viewRef={markupView} sliceRef={sliceRef} />
<div <div
ref={markupHost} ref={markupHost}
className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-md border bg-muted/20 min-h-[min(58dvh,32rem)]" className="min-h-[24rem] h-[min(84vh,56rem)] overflow-hidden rounded-md border bg-muted/20"
/> />
</TabsContent> </TabsContent>
<TabsContent <TabsContent
value="preview" value="preview"
className="mt-0 flex min-h-0 flex-1 flex-col overflow-hidden data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0" className="mt-0 data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0"
> >
<div className="flex min-h-0 flex-1 overflow-y-auto rounded-md border border-border bg-background py-2 text-left"> <div className="min-h-[24rem] h-[min(84vh,56rem)] overflow-y-auto rounded-md border border-border bg-background py-2 text-left">
<AdvancedEventLabPreviewPane <AdvancedEventLabPreviewPane
markupMode={markupMode} markupMode={markupMode}
source={previewDoc} source={previewDoc}
@ -1117,18 +943,198 @@ export default function AdvancedEventLabDialog({
</Tabs> </Tabs>
</div> </div>
{formatToolbar ? ( {formatToolbar ? (
<div className="shrink-0 border-t bg-muted/20 px-2 py-2">{formatToolbar}</div> <div className="mt-2 border-t bg-muted/20 px-2 py-2">{formatToolbar}</div>
) : null} ) : null}
<DialogFooter className="shrink-0 px-4 py-3 border-t gap-2"> <div className="mt-2 border-t bg-background px-4 py-3">
<Button type="button" variant="outline" onClick={() => handleDialogOpenChange(false)}> <div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
{t('Advanced lab cancel undo')} <Button type="button" variant="outline" onClick={() => handleDialogOpenChange(false)}>
</Button> {t('Advanced lab cancel undo')}
<Button type="button" onClick={handleApply}> </Button>
{t('Apply')} <Button type="button" onClick={handleApply}>
</Button> {t('Apply')}
</DialogFooter> </Button>
</div>
</div>
<div className="border-t bg-background px-4 pb-4 pt-3">
<div className="flex flex-wrap items-end gap-3">
{isLanguageToolConfigured() ? (
<div className="min-w-[10rem] space-y-1">
<Label htmlFor="lt-lang">{t('Advanced lab grammar language')}</Label>
<Select
value={ltLang}
onValueChange={(code) => {
logger.info('[AdvancedLab] grammar language changed', { from: ltLang, to: code })
setLtLang(code)
}}
>
<SelectTrigger id="lt-lang" className="min-w-[220px] max-w-md w-auto">
<SelectValue />
</SelectTrigger>
<SelectContent
className="max-h-64 min-w-[var(--radix-select-trigger-width)] p-0"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div
className="sticky top-0 z-10 border-b border-border bg-popover p-2"
onPointerDown={(e) => e.stopPropagation()}
>
<Input
type="search"
value={ltLangFilter}
onChange={(e) => setLtLangFilter(e.target.value)}
placeholder={t('Language list filter placeholder')}
className="h-8"
aria-label={t('Language list filter placeholder')}
/>
</div>
<div className="py-1">
{ltListFiltered.map((code) => (
<SelectItem
key={code}
value={code}
className="items-start py-2.5 whitespace-normal"
>
<LanguageSelectOptionLines tag={code} className="w-full" />
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
) : null}
{isTranslateConfigured() ? (
<div className="flex flex-col gap-2 min-w-0">
{translateLoad === 'idle' || translateLoad === 'loading' ? (
<p className="text-xs text-muted-foreground">{t('Advanced lab translation languages loading')}</p>
) : null}
{translateLoad === 'ready' ? (
<div className="flex flex-wrap items-end gap-3">
<div className="space-y-1 min-w-[10rem]">
<Label htmlFor="tr-src">{t('Advanced lab translation source')}</Label>
<Select
value={translateSource}
onValueChange={(v) => {
logger.info('[AdvancedLab] translation source language changed', {
from: translateSource,
to: v
})
setTranslateSource(v)
if (v !== 'auto' && v === translateTarget) {
const alt = translateLangs.find((l) => l.code !== v)?.code
if (alt) setTranslateTarget(alt)
}
}}
>
<SelectTrigger id="tr-src" className="min-w-[220px] max-w-md w-auto">
<SelectValue />
</SelectTrigger>
<SelectContent
className="max-h-64 min-w-[var(--radix-select-trigger-width)] p-0"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div
className="sticky top-0 z-10 border-b border-border bg-popover p-2"
onPointerDown={(e) => e.stopPropagation()}
>
<Input
type="search"
value={translateSrcFilter}
onChange={(e) => setTranslateSrcFilter(e.target.value)}
placeholder={t('Language list filter placeholder')}
className="h-8"
aria-label={t('Language list filter placeholder')}
/>
</div>
<div className="py-1">
{showTranslateSourceAuto ? (
<SelectItem value="auto">{t('Advanced lab translation source auto')}</SelectItem>
) : null}
{translateLangsFilteredSrc.map((l) => (
<SelectItem
key={l.code}
value={l.code}
className="items-start py-2.5 whitespace-normal"
>
<LanguageSelectOptionLines tag={l.code} className="w-full" />
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
<div className="space-y-1 min-w-[10rem]">
<Label htmlFor="tr-tgt">{t('Advanced lab translation target')}</Label>
<Select
value={translateTarget}
onValueChange={(v) => {
logger.info('[AdvancedLab] translation target language changed', {
from: translateTarget,
to: v
})
setTranslateTarget(v)
if (translateSource !== 'auto' && v === translateSource) {
setTranslateSource('auto')
}
}}
>
<SelectTrigger id="tr-tgt" className="min-w-[220px] max-w-md w-auto">
<SelectValue />
</SelectTrigger>
<SelectContent
className="max-h-64 min-w-[var(--radix-select-trigger-width)] p-0"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div
className="sticky top-0 z-10 border-b border-border bg-popover p-2"
onPointerDown={(e) => e.stopPropagation()}
>
<Input
type="search"
value={translateTgtFilter}
onChange={(e) => setTranslateTgtFilter(e.target.value)}
placeholder={t('Language list filter placeholder')}
className="h-8"
aria-label={t('Language list filter placeholder')}
/>
</div>
<div className="py-1">
{translateLangsFilteredTgt.map((l) => (
<SelectItem
key={l.code}
value={l.code}
className="items-start py-2.5 whitespace-normal"
>
<LanguageSelectOptionLines tag={l.code} className="w-full" />
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
<Button type="button" variant="secondary" size="sm" onClick={() => void handleTranslate()}>
{t('Advanced lab translate')}
</Button>
</div>
) : null}
{translateLoad === 'empty' ? (
<p className="text-xs text-destructive">{t('Advanced lab translation languages empty')}</p>
) : null}
{translateLoad === 'error' ? (
<p className="text-xs text-destructive">{t('Advanced lab translation languages error')}</p>
) : null}
</div>
) : null}
{contextEventId && isTranslateConfigured() ? (
<Button type="button" variant="outline" size="sm" onClick={handleReadAloudBuffer}>
{t('Advanced lab use translation read aloud')}
</Button>
) : null}
</div>
</div>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) )
@ -1137,9 +1143,10 @@ export default function AdvancedEventLabDialog({
/** Responsive shell: ~5× prior max width cap and ~3× vertical use of viewport (still clamped). */ /** Responsive shell: ~5× prior max width cap and ~3× vertical use of viewport (still clamped). */
function cnDialogShell(): string { function cnDialogShell(): string {
return [ return [
'z-[250] max-w-none flex min-h-0 flex-col gap-0 overflow-hidden p-0', 'z-[250] !flex max-w-none flex-col gap-0 overflow-hidden p-0',
'w-[min(98vw,calc(72rem*5))]', 'w-[min(98vw,calc(72rem*5))]',
'h-[min(94vh,calc(28rem*3))]', 'max-h-[min(96vh,90dvh)]',
'max-h-[min(96vh,90dvh)]' 'top-[max(0.5rem,env(safe-area-inset-top,0px))] !translate-y-0',
'sm:top-[50%] sm:!translate-y-[-50%]'
].join(' ') ].join(' ')
} }

Loading…
Cancel
Save