Projekte: Preview-Block ohne Kleinknoepfe, Statuspunkt als Badge, Log auf hellem Grund
Markup auf Bausteine (Button, Badge) und die Klassen pv-*, input, select, msg, link umgestellt; keine Inline-Stile, kein btn-sm, kein Farbliteral. Fehlertexte des Runners behalten ihren Zeilenumbruch ueber .pv-block .msg. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
92fef9d4a5
commit
154aea44a8
2 files changed
+55
-66
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { modeOptionsFor, previewStatus, modeHint, isDevUnavailableError, NO_DEV_HINT } from '../lib/previewView'
|
||||
import { isValidEnvName, buildEnvPayload, isDevPreview, dotColor, envValueTooLong, MAX_ENV_VARS } from '../lib/previewEnv'
|
||||
import { isValidEnvName, buildEnvPayload, isDevPreview, envValueTooLong, MAX_ENV_VARS } from '../lib/previewEnv'
|
||||
import {
|
||||
fetchPreview,
|
||||
setPreviewMode,
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
stopPreviewDev,
|
||||
fetchPreviewLog,
|
||||
} from '../lib/previewApi'
|
||||
import { Button, Badge } from './ui'
|
||||
import { previewColorTone } from './projectTones'
|
||||
|
||||
const STATE_OF_COLOR = { green: 'online', yellow: 'building', red: 'failed', grey: 'off' }
|
||||
const NOT_ACTIVE = 'Preview-Runner ist nicht aktiv'
|
||||
|
||||
const logDate = (at) => {
|
||||
@@ -171,7 +172,7 @@ export default function PreviewBlock({ projectId }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div className="text-grey" style={{ fontSize: 12 }}>Lädt …</div>
|
||||
if (loading) return <p className="muted xs">Lädt …</p>
|
||||
|
||||
const view = data ? { ...data } : { state: 'off' }
|
||||
const status = previewStatus(view)
|
||||
@@ -182,18 +183,15 @@ export default function PreviewBlock({ projectId }) {
|
||||
const dev = isDevPreview(data)
|
||||
const logs = [...(data?.logs || [])].sort((a, b) => new Date(b.at) - new Date(a.at)).slice(0, 5)
|
||||
|
||||
const small = { fontSize: 12 }
|
||||
|
||||
return (
|
||||
<div style={{ fontSize: 13 }}>
|
||||
<div className="pv-block">
|
||||
{data && (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
|
||||
<label className="text-grey" htmlFor={`pv-mode-${projectId}`} style={small}>Modus</label>
|
||||
<div className="pv-row">
|
||||
<label className="muted xs" htmlFor={`pv-mode-${projectId}`}>Modus</label>
|
||||
<select
|
||||
id={`pv-mode-${projectId}`}
|
||||
className="form-control"
|
||||
style={{ maxWidth: 200 }}
|
||||
className="select pv-mode"
|
||||
value={mode}
|
||||
disabled={busy || inactive}
|
||||
onChange={(e) => run(() => setPreviewMode(projectId, e.target.value))}
|
||||
@@ -201,47 +199,45 @@ export default function PreviewBlock({ projectId }) {
|
||||
{modeOptionsFor(data.devAvailable).map((o) => <option key={o.value} value={o.value} disabled={o.disabled}>{o.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
{data.devAvailable === false && <div className="text-grey" style={{ ...small, marginTop: 4 }}>{NO_DEV_HINT}</div>}
|
||||
{hint && <div className="text-grey" style={{ ...small, marginTop: 4 }}>{hint}</div>}
|
||||
<div style={{ marginTop: 8, display: 'flex', gap: 6, alignItems: 'flex-start' }}>
|
||||
<span style={{ width: 10, height: 10, borderRadius: '50%', background: dotColor(STATE_OF_COLOR[status.color]), display: 'inline-block', flexShrink: 0, marginTop: 4 }} />
|
||||
<span style={{ overflowWrap: 'anywhere' }}>{status.text}</span>
|
||||
{data.devAvailable === false && <p className="muted xs">{NO_DEV_HINT}</p>}
|
||||
{hint && <p className="muted xs">{hint}</p>}
|
||||
<div className="pv-row">
|
||||
<Badge tone={previewColorTone(status.color)} className="wrap">{status.text}</Badge>
|
||||
</div>
|
||||
{data.state === 'failed' && data.error && !isDevUnavailableError(data.error) && (
|
||||
<div style={{ color: 'var(--red-fg)', ...small, marginTop: 4, overflowWrap: 'anywhere' }}>{data.error}</div>
|
||||
<p className="msg err">{data.error}</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{inactive && <div className="text-grey" style={{ ...small, marginTop: 8 }}>{NOT_ACTIVE}.</div>}
|
||||
{error && <div style={{ color: 'var(--red-fg)', ...small, marginTop: 8, overflowWrap: 'anywhere' }}>{error}</div>}
|
||||
{inactive && <p className="muted xs">{NOT_ACTIVE}.</p>}
|
||||
{error && <p className="msg err" role="alert">{error}</p>}
|
||||
|
||||
{data && !inactive && (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 10 }}>
|
||||
<button type="button" className="btn btn-sm" disabled={busy || state === 'building'} onClick={() => run(() => rebuildPreview(projectId))}>
|
||||
<div className="pv-row">
|
||||
<Button type="button" disabled={busy || state === 'building'} onClick={() => run(() => rebuildPreview(projectId))}>
|
||||
Neu bauen
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm" disabled={busy || logs.length === 0} onClick={() => setShowLogs((v) => !v)}>
|
||||
</Button>
|
||||
<Button type="button" disabled={busy || logs.length === 0} onClick={() => setShowLogs((v) => !v)}>
|
||||
Protokoll ansehen
|
||||
</button>
|
||||
</Button>
|
||||
{dev && (
|
||||
<>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={() => run(() => startPreviewDev(projectId))}>
|
||||
<Button type="button" disabled={busy} onClick={() => run(() => startPreviewDev(projectId))}>
|
||||
Jetzt starten
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={() => run(() => stopPreviewDev(projectId))}>
|
||||
</Button>
|
||||
<Button type="button" disabled={busy} onClick={() => run(() => stopPreviewDev(projectId))}>
|
||||
Stoppen
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showLogs && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<>
|
||||
<select
|
||||
className="form-control"
|
||||
style={{ maxWidth: '100%' }}
|
||||
className="select"
|
||||
aria-label="Lauf wählen"
|
||||
value={logFile}
|
||||
disabled={busy}
|
||||
@@ -250,47 +246,39 @@ export default function PreviewBlock({ projectId }) {
|
||||
<option value="">Lauf wählen …</option>
|
||||
{logs.map((l) => <option key={l.runFile} value={l.runFile}>{logDate(l.at)}</option>)}
|
||||
</select>
|
||||
{logFile && (
|
||||
<pre style={{ marginTop: 6, maxHeight: 260, overflow: 'auto', background: 'var(--bg-subtle)', padding: 8, borderRadius: 6, fontFamily: 'monospace', fontSize: 11, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
||||
{logText || '…'}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
{logFile && <pre className="pv-log">{logText || '…'}</pre>}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<strong style={small}>Variablen</strong>
|
||||
<div style={{ marginTop: 6 }}>
|
||||
{names.length === 0 && <div className="text-grey" style={small}>Keine Variablen.</div>}
|
||||
{names.map((n) => (
|
||||
<div key={n} style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', padding: '4px 0', borderBottom: '1px solid var(--line)' }}>
|
||||
<code style={{ flex: '1 1 140px', overflowWrap: 'anywhere' }}>{n}</code>
|
||||
<span className="text-grey">•••</span>
|
||||
{editName === n ? (
|
||||
<>
|
||||
<input className="form-control" style={{ flex: '1 1 120px', minWidth: 0 }} type="password" autoComplete="off" placeholder="Neuer Wert" value={editValue} onKeyDown={enter(handleChange)} onChange={(e) => setEditValue(e.target.value)} />
|
||||
<button type="button" className="btn btn-sm" onClick={handleChange}>Übernehmen</button>
|
||||
</>
|
||||
) : (
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={() => { setEditName(n); setEditValue(''); setEnvError('') }}>Ändern</button>
|
||||
)}
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={() => handleRemove(n)}>Entfernen</button>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', marginTop: 8 }}>
|
||||
<input type="text" className="form-control" style={{ flex: '1 1 140px', minWidth: 0 }} placeholder="NAME" onKeyDown={enter(handleAdd)} value={newName} onChange={(e) => setNewName(e.target.value)} />
|
||||
<input type="password" autoComplete="off" className="form-control" style={{ flex: '1 1 140px', minWidth: 0 }} placeholder="Wert" onKeyDown={enter(handleAdd)} value={newValue} onChange={(e) => setNewValue(e.target.value)} />
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={handleAdd}>Hinzufügen</button>
|
||||
</div>
|
||||
{envError && <div style={{ color: 'var(--red-fg)', ...small, marginTop: 4 }}>{envError}</div>}
|
||||
{dirty && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<button type="button" className="btn btn-sm" disabled={busy} onClick={handleSaveEnv}>Variablen speichern</button>
|
||||
</div>
|
||||
<h4 className="pv-title">Variablen</h4>
|
||||
{names.length === 0 && <p className="muted xs">Keine Variablen.</p>}
|
||||
{names.map((n) => (
|
||||
<div key={n} className="pv-var">
|
||||
<code>{n}</code>
|
||||
<span className="muted">•••</span>
|
||||
{editName === n ? (
|
||||
<>
|
||||
<input className="input" type="password" autoComplete="off" placeholder="Neuer Wert" value={editValue} onKeyDown={enter(handleChange)} onChange={(e) => setEditValue(e.target.value)} />
|
||||
<button type="button" className="link" onClick={handleChange}>Übernehmen</button>
|
||||
</>
|
||||
) : (
|
||||
<button type="button" className="link" disabled={busy} onClick={() => { setEditName(n); setEditValue(''); setEnvError('') }}>Ändern</button>
|
||||
)}
|
||||
<div className="text-grey" style={{ ...small, marginTop: 6 }}>Alles mit VITE_ / NEXT_PUBLIC_ ist später im Browser sichtbar.</div>
|
||||
<button type="button" className="link" disabled={busy} onClick={() => handleRemove(n)}>Entfernen</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="pv-add">
|
||||
<input type="text" className="input" placeholder="NAME" onKeyDown={enter(handleAdd)} value={newName} onChange={(e) => setNewName(e.target.value)} />
|
||||
<input type="password" autoComplete="off" className="input" placeholder="Wert" onKeyDown={enter(handleAdd)} value={newValue} onChange={(e) => setNewValue(e.target.value)} />
|
||||
<Button type="button" disabled={busy} onClick={handleAdd}>Hinzufügen</Button>
|
||||
</div>
|
||||
{envError && <p className="msg err">{envError}</p>}
|
||||
{dirty && (
|
||||
<div className="pv-row">
|
||||
<Button type="button" disabled={busy} onClick={handleSaveEnv}>Variablen speichern</Button>
|
||||
</div>
|
||||
)}
|
||||
<p className="muted xs">Alles mit VITE_ / NEXT_PUBLIC_ ist später im Browser sichtbar.</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1150,6 +1150,7 @@ button.link { text-align: left; } /* Reset und Gewicht von button.link stehen im
|
||||
.pv-var { padding-bottom: 8px; border-bottom: 1px solid var(--line); }
|
||||
.pv-mode { width: 200px; max-width: 100%; }
|
||||
.pv-title { font-size: 13px; font-weight: 600; }
|
||||
.pv-block .msg { overflow-wrap: anywhere; } /* Fehlertexte des Runners enthalten lange Pfade ohne Leerzeichen */
|
||||
.pv-log { background: var(--bg-subtle); max-height: 260px; overflow: auto; white-space: pre-wrap; word-break: break-all; padding: 8px; border-radius: var(--radius); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; line-height: 16px; }
|
||||
.pv-var code, .pv-var .input, .pv-add .input { flex: 1 1 140px; min-width: 0; }
|
||||
.pv-var code { overflow-wrap: anywhere; }
|
||||
|
||||
Reference in new issue
Block a user