feat(editor): localised analyzer/strip/run messages + monospace font fallback
Closes operator-reported issues around the text-tab font and
the English-only run + diagnostic surfaces.
- **Real monospace everywhere**. The package never bundled
JetBrains Mono as an asset (it relied on Studio's
google_fonts pre-load), so a host that doesn't ship the
font saw the YAML editor render in the system proportional
default. New _monoTextStyle() pins fontFamilyFallback to a
cross-platform monospace chain (Menlo / Consolas / Courier
New / monospace) so the editor stays a grid in every host.
Applied to the code field, gutter, diagnostic strip,
issue rows, hover card, fix buttons, and the run tab's
error box.
- **Locale-aware analyzer messages**. New AnalyzerStrings
adapter holds every string the analyzer emits, with an
.english default + an .from(FlowEditorStrings) factory.
FlowYamlCodeController.setCapabilityProviders takes the
strings; FlowEditorPage wires them from the active locale.
The analyzer's 'Unknown capability', 'Did you mean',
'Not in the store — install locally with fai install
--link', 'Unknown input/output type', YAML parse errors,
and every quick-fix label (Install / Add source / Use /
Change to) now flip to DE when the editor's locale is DE.
- **Localised run + diagnostic chrome**. The bottom strip's
'No issues', '2 errors · 1 warning', 'Copy all'; the
issue row's L-prefix + Copy tooltip; the hover card's
L-prefix + Copy tooltip; the run-block tooltip + inline
message ('2 Fehler verhindern den Lauf · siehe
Diagnose-Leiste unten'); the step-row 'awaiting approval'
suffix; the CopyableErrorBox's Copy / Copied tooltip —
all now flow through FlowEditorStrings.
Bumped to 0.20.0.
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
b6bb8741a9
commit
5cd2745db5
6 changed files with 306 additions and 97 deletions
|
|
@ -20,6 +20,7 @@
|
||||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
|
import 'l10n.dart';
|
||||||
import 'quick_fix.dart';
|
import 'quick_fix.dart';
|
||||||
import 'wire_colors.dart';
|
import 'wire_colors.dart';
|
||||||
|
|
||||||
|
|
@ -59,9 +60,15 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
/// [kOutputsNodeId] sentinels.
|
/// [kOutputsNodeId] sentinels.
|
||||||
final Map<String, IssueType> _stepSeverity = {};
|
final Map<String, IssueType> _stepSeverity = {};
|
||||||
|
|
||||||
|
/// Localised string builders the analyzer pipes its messages
|
||||||
|
/// through. Default is English so the analyzer behaves the
|
||||||
|
/// same in tests + host-less callers.
|
||||||
|
final AnalyzerStrings strings;
|
||||||
|
|
||||||
FlowAnalyzer({
|
FlowAnalyzer({
|
||||||
required this.availableCapabilities,
|
required this.availableCapabilities,
|
||||||
this.storeCapabilities,
|
this.storeCapabilities,
|
||||||
|
this.strings = AnalyzerStrings.english,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Read-only view of the fixes computed during the last
|
/// Read-only view of the fixes computed during the last
|
||||||
|
|
@ -115,14 +122,18 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
issues.add(
|
issues.add(
|
||||||
Issue(
|
Issue(
|
||||||
line: e.span?.start.line ?? 0,
|
line: e.span?.start.line ?? 0,
|
||||||
message: 'YAML: ${e.message}',
|
message: strings.yamlError(e.message),
|
||||||
type: IssueType.error,
|
type: IssueType.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return AnalysisResult(issues: issues);
|
return AnalysisResult(issues: issues);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
issues.add(
|
issues.add(
|
||||||
Issue(line: 0, message: 'YAML: $e', type: IssueType.error),
|
Issue(
|
||||||
|
line: 0,
|
||||||
|
message: strings.yamlError(e.toString()),
|
||||||
|
type: IssueType.error,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return AnalysisResult(issues: issues);
|
return AnalysisResult(issues: issues);
|
||||||
}
|
}
|
||||||
|
|
@ -159,16 +170,12 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
|
|
||||||
// Tailor the message: "unknown" reads identical in
|
// Tailor the message: "unknown" reads identical in
|
||||||
// both cases, but the recovery line nudges toward
|
// both cases, but the recovery line nudges toward
|
||||||
// the right action.
|
// the right action. Localised via [strings].
|
||||||
final message = inStore
|
final message = inStore
|
||||||
? 'Unknown capability "$useValue". '
|
? strings.unknownCapInStore(useValue)
|
||||||
'Install via the Fix button or check the spelling.'
|
|
||||||
: didYouMean != null
|
: didYouMean != null
|
||||||
? 'Unknown capability "$useValue". '
|
? strings.unknownCapTypo(useValue, didYouMean)
|
||||||
'Did you mean "$didYouMean"?'
|
: strings.unknownCapNotInStore(useValue);
|
||||||
: 'Unknown capability "$useValue". '
|
|
||||||
'Not in the store — install locally with '
|
|
||||||
'`fai install --link <path>` or check the spelling.';
|
|
||||||
final issue = Issue(
|
final issue = Issue(
|
||||||
line: issueLine,
|
line: issueLine,
|
||||||
message: message,
|
message: message,
|
||||||
|
|
@ -192,7 +199,7 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
ReplaceLineValueFix(
|
ReplaceLineValueFix(
|
||||||
line: issueLine,
|
line: issueLine,
|
||||||
replacement: fullReplacement,
|
replacement: fullReplacement,
|
||||||
label: 'Use "$fullReplacement"',
|
label: strings.fixUseInstead(fullReplacement),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -200,20 +207,14 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
fixes.add(
|
fixes.add(
|
||||||
InstallCapabilityFix(
|
InstallCapabilityFix(
|
||||||
capability: useValue,
|
capability: useValue,
|
||||||
label: 'Install $useValue',
|
label: strings.fixInstallCap(useValue),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (didYouMean == null) {
|
} else if (didYouMean == null) {
|
||||||
// Not in store + no near-miss spelling — give the
|
|
||||||
// operator a path to register the module they
|
|
||||||
// actually have (local clone, internal URL, …).
|
|
||||||
// Hidden behind the "Did you mean" suggestion when
|
|
||||||
// present so the suggested fix stays the primary
|
|
||||||
// action.
|
|
||||||
fixes.add(
|
fixes.add(
|
||||||
AddModuleSourceFix(
|
AddModuleSourceFix(
|
||||||
capability: useValue,
|
capability: useValue,
|
||||||
label: 'Add source for $useValue…',
|
label: strings.fixAddSource(useValue),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -226,13 +227,13 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
|
|
||||||
_checkFieldTypes(
|
_checkFieldTypes(
|
||||||
doc['inputs'],
|
doc['inputs'],
|
||||||
'input',
|
strings.inputKind(),
|
||||||
issues,
|
issues,
|
||||||
nodeId: kInputsNodeId,
|
nodeId: kInputsNodeId,
|
||||||
);
|
);
|
||||||
_checkFieldTypes(
|
_checkFieldTypes(
|
||||||
doc['outputs'],
|
doc['outputs'],
|
||||||
'output',
|
strings.outputKind(),
|
||||||
issues,
|
issues,
|
||||||
nodeId: kOutputsNodeId,
|
nodeId: kOutputsNodeId,
|
||||||
);
|
);
|
||||||
|
|
@ -278,23 +279,22 @@ class FlowAnalyzer extends AbstractAnalyzer {
|
||||||
if (!kKnownTypes.contains(value)) {
|
if (!kKnownTypes.contains(value)) {
|
||||||
final issue = Issue(
|
final issue = Issue(
|
||||||
line: entry.value.span.start.line,
|
line: entry.value.span.start.line,
|
||||||
message:
|
message: strings.unknownType(
|
||||||
'Unknown $kind type "$value". '
|
kind,
|
||||||
'Use one of: ${kKnownTypes.join(", ")}.',
|
value,
|
||||||
|
kKnownTypes.join(", "),
|
||||||
|
),
|
||||||
type: IssueType.warning,
|
type: IssueType.warning,
|
||||||
);
|
);
|
||||||
out.add(issue);
|
out.add(issue);
|
||||||
_bumpSeverity(nodeId, IssueType.warning);
|
_bumpSeverity(nodeId, IssueType.warning);
|
||||||
// If the typo is within edit-distance two of a known
|
|
||||||
// type token, offer a one-click replace as the primary
|
|
||||||
// fix. The strip and tooltip render this as a button.
|
|
||||||
final closest = _closestKnownType(value);
|
final closest = _closestKnownType(value);
|
||||||
if (closest != null) {
|
if (closest != null) {
|
||||||
_fixesByIssue[issue] = [
|
_fixesByIssue[issue] = [
|
||||||
ReplaceLineValueFix(
|
ReplaceLineValueFix(
|
||||||
line: entry.value.span.start.line,
|
line: entry.value.span.start.line,
|
||||||
replacement: closest,
|
replacement: closest,
|
||||||
label: 'Change to "$closest"',
|
label: strings.fixChangeTo(closest),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@ class _FlowEditorPageState extends State<FlowEditorPage>
|
||||||
_controller.codeController.setCapabilityProviders(
|
_controller.codeController.setCapabilityProviders(
|
||||||
available: () => widget.availableCapabilities,
|
available: () => widget.availableCapabilities,
|
||||||
store: () => widget.storeCapabilities,
|
store: () => widget.storeCapabilities,
|
||||||
|
strings: AnalyzerStrings.from(_l),
|
||||||
);
|
);
|
||||||
_controller.addListener(_onCtrlChanged);
|
_controller.addListener(_onCtrlChanged);
|
||||||
_controller.codeController.hoverRequest.addListener(_onHoverChanged);
|
_controller.codeController.hoverRequest.addListener(_onHoverChanged);
|
||||||
|
|
@ -175,6 +176,7 @@ class _FlowEditorPageState extends State<FlowEditorPage>
|
||||||
_hoverEntry = OverlayEntry(
|
_hoverEntry = OverlayEntry(
|
||||||
builder: (ctx) => _IssueHoverCard(
|
builder: (ctx) => _IssueHoverCard(
|
||||||
request: req,
|
request: req,
|
||||||
|
strings: _l,
|
||||||
onApplyFix: _applyQuickFix,
|
onApplyFix: _applyQuickFix,
|
||||||
onEnter: _controller.codeController.cancelHoverDismiss,
|
onEnter: _controller.codeController.cancelHoverDismiss,
|
||||||
onExit: _controller.codeController.scheduleHoverDismiss,
|
onExit: _controller.codeController.scheduleHoverDismiss,
|
||||||
|
|
@ -205,6 +207,7 @@ class _FlowEditorPageState extends State<FlowEditorPage>
|
||||||
_controller.codeController.setCapabilityProviders(
|
_controller.codeController.setCapabilityProviders(
|
||||||
available: () => newCaps,
|
available: () => newCaps,
|
||||||
store: () => widget.storeCapabilities,
|
store: () => widget.storeCapabilities,
|
||||||
|
strings: AnalyzerStrings.from(_l),
|
||||||
);
|
);
|
||||||
await _controller.codeController.reanalyze();
|
await _controller.codeController.reanalyze();
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +220,7 @@ class _FlowEditorPageState extends State<FlowEditorPage>
|
||||||
_controller.codeController.setCapabilityProviders(
|
_controller.codeController.setCapabilityProviders(
|
||||||
available: () => newCaps,
|
available: () => newCaps,
|
||||||
store: () => widget.storeCapabilities,
|
store: () => widget.storeCapabilities,
|
||||||
|
strings: AnalyzerStrings.from(_l),
|
||||||
);
|
);
|
||||||
await _controller.codeController.reanalyze();
|
await _controller.codeController.reanalyze();
|
||||||
}
|
}
|
||||||
|
|
@ -578,6 +582,7 @@ outputs:
|
||||||
// the message + quick-fix buttons.
|
// the message + quick-fix buttons.
|
||||||
_DiagnosticStrip(
|
_DiagnosticStrip(
|
||||||
controller: _controller.codeController,
|
controller: _controller.codeController,
|
||||||
|
strings: _l,
|
||||||
onApplyFix: _applyQuickFix,
|
onApplyFix: _applyQuickFix,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -719,9 +724,15 @@ outputs:
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _textTab(ThemeData theme) {
|
Widget _textTab(ThemeData theme) {
|
||||||
final mono = TextStyle(
|
// The editor itself doesn't bundle JetBrains Mono as an
|
||||||
fontFamily: 'JetBrains Mono',
|
// asset (Studio preloads it via google_fonts). When the
|
||||||
fontSize: 13,
|
// host isn't Studio, Flutter would fall back to a
|
||||||
|
// proportional default and the code would line up like
|
||||||
|
// ransom-note prose. _monoTextStyle's fontFamilyFallback
|
||||||
|
// pins the chain to the system monospace stack so a YAML
|
||||||
|
// grid stays a grid in every host.
|
||||||
|
final mono = _monoTextStyle(
|
||||||
|
size: 13,
|
||||||
height: 1.45,
|
height: 1.45,
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
);
|
);
|
||||||
|
|
@ -757,11 +768,7 @@ outputs:
|
||||||
Map<String, TextStyle> _yamlStyle(ThemeData theme) {
|
Map<String, TextStyle> _yamlStyle(ThemeData theme) {
|
||||||
final cs = theme.colorScheme;
|
final cs = theme.colorScheme;
|
||||||
final isDark = theme.brightness == Brightness.dark;
|
final isDark = theme.brightness == Brightness.dark;
|
||||||
final monoBase = TextStyle(
|
final monoBase = _monoTextStyle(size: 13, height: 1.45);
|
||||||
fontFamily: 'JetBrains Mono',
|
|
||||||
fontSize: 13,
|
|
||||||
height: 1.45,
|
|
||||||
);
|
|
||||||
// Pick clearly-distinguished hues so keys, strings,
|
// Pick clearly-distinguished hues so keys, strings,
|
||||||
// numbers, anchors, and comments don't blur into each
|
// numbers, anchors, and comments don't blur into each
|
||||||
// other. We derive accents from the active ColorScheme
|
// other. We derive accents from the active ColorScheme
|
||||||
|
|
@ -1002,8 +1009,7 @@ class _TabActionStrip extends StatelessWidget {
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: FaiSpace.sm),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: errorCount > 0
|
message: errorCount > 0
|
||||||
? '$errorCount error${errorCount == 1 ? '' : 's'} '
|
? strings.runErrorsBlockTooltip(errorCount)
|
||||||
'prevent the run — fix them first'
|
|
||||||
: '',
|
: '',
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: onRun,
|
onPressed: onRun,
|
||||||
|
|
@ -1277,11 +1283,44 @@ class _NewFlowDialogState extends State<_NewFlowDialog> {
|
||||||
/// the trackpad-only operator. When the analyzer attaches a
|
/// the trackpad-only operator. When the analyzer attaches a
|
||||||
/// `QuickFix` to an issue, the row also renders an action
|
/// `QuickFix` to an issue, the row also renders an action
|
||||||
/// button (e.g. "Install <cap>" or "Change to \"bytes\"").
|
/// button (e.g. "Install <cap>" or "Change to \"bytes\"").
|
||||||
|
/// Build a TextStyle that prefers `JetBrains Mono` (Studio
|
||||||
|
/// preloads it via `google_fonts`) but falls back to the
|
||||||
|
/// system's generic `monospace` family when the bundled font
|
||||||
|
/// isn't registered — which is what happens when this package
|
||||||
|
/// is hosted by something other than Studio. Without the
|
||||||
|
/// fallback the field rendered with the default proportional
|
||||||
|
/// font and code lined up like ransom-note prose.
|
||||||
|
TextStyle _monoTextStyle({
|
||||||
|
required double size,
|
||||||
|
Color? color,
|
||||||
|
FontWeight? weight,
|
||||||
|
double? letterSpacing,
|
||||||
|
double? height,
|
||||||
|
}) {
|
||||||
|
return TextStyle(
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontFamilyFallback: const [
|
||||||
|
'JetBrainsMono Nerd Font',
|
||||||
|
'Menlo',
|
||||||
|
'Consolas',
|
||||||
|
'Courier New',
|
||||||
|
'monospace',
|
||||||
|
],
|
||||||
|
fontSize: size,
|
||||||
|
color: color,
|
||||||
|
fontWeight: weight,
|
||||||
|
letterSpacing: letterSpacing,
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class _DiagnosticStrip extends StatefulWidget {
|
class _DiagnosticStrip extends StatefulWidget {
|
||||||
final FlowYamlCodeController controller;
|
final FlowYamlCodeController controller;
|
||||||
|
final FlowEditorStrings strings;
|
||||||
final Future<void> Function(QuickFix) onApplyFix;
|
final Future<void> Function(QuickFix) onApplyFix;
|
||||||
const _DiagnosticStrip({
|
const _DiagnosticStrip({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
|
required this.strings,
|
||||||
required this.onApplyFix,
|
required this.onApplyFix,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1324,7 +1363,9 @@ class _DiagnosticStripState extends State<_DiagnosticStrip> {
|
||||||
String _formatAll(List<Issue> issues) {
|
String _formatAll(List<Issue> issues) {
|
||||||
final buf = StringBuffer();
|
final buf = StringBuffer();
|
||||||
for (final i in issues) {
|
for (final i in issues) {
|
||||||
buf.writeln('L${i.line + 1}: ${i.message}');
|
buf.writeln(
|
||||||
|
'${widget.strings.diagnosticLinePrefix(i.line + 1)}: ${i.message}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return buf.toString().trimRight();
|
return buf.toString().trimRight();
|
||||||
}
|
}
|
||||||
|
|
@ -1354,12 +1395,11 @@ class _DiagnosticStripState extends State<_DiagnosticStrip> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'No issues',
|
widget.strings.diagnosticNoIssues,
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 10,
|
||||||
fontSize: 10,
|
|
||||||
letterSpacing: 0.4,
|
|
||||||
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||||
|
letterSpacing: 0.4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -1402,27 +1442,25 @@ class _DiagnosticStripState extends State<_DiagnosticStrip> {
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
_summary(errorCount, warnCount),
|
_summary(errorCount, warnCount),
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 11,
|
||||||
fontSize: 11,
|
weight: FontWeight.w600,
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: tone,
|
color: tone,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
'L${issues.first.line + 1}: ${issues.first.message}',
|
'${widget.strings.diagnosticLinePrefix(issues.first.line + 1)}: ${issues.first.message}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 11,
|
||||||
fontSize: 11,
|
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Copy all',
|
tooltip: widget.strings.diagnosticCopyAll,
|
||||||
icon: const Icon(Icons.content_copy, size: 14),
|
icon: const Icon(Icons.content_copy, size: 14),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
onPressed: () => _copy(_formatAll(issues)),
|
onPressed: () => _copy(_formatAll(issues)),
|
||||||
|
|
@ -1453,10 +1491,12 @@ class _DiagnosticStripState extends State<_DiagnosticStrip> {
|
||||||
issue: issue,
|
issue: issue,
|
||||||
fixes: widget.controller.fixesFor(issue),
|
fixes: widget.controller.fixesFor(issue),
|
||||||
tone: _toneForIssue(issue.type, theme),
|
tone: _toneForIssue(issue.type, theme),
|
||||||
|
strings: widget.strings,
|
||||||
busy: _busy,
|
busy: _busy,
|
||||||
onApplyFix: _runFix,
|
onApplyFix: _runFix,
|
||||||
onCopy: () =>
|
onCopy: () => _copy(
|
||||||
_copy('L${issue.line + 1}: ${issue.message}'),
|
'${widget.strings.diagnosticLinePrefix(issue.line + 1)}: ${issue.message}',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -1471,9 +1511,9 @@ class _DiagnosticStripState extends State<_DiagnosticStrip> {
|
||||||
|
|
||||||
String _summary(int errors, int warnings) {
|
String _summary(int errors, int warnings) {
|
||||||
final parts = <String>[];
|
final parts = <String>[];
|
||||||
if (errors > 0) parts.add('$errors error${errors == 1 ? '' : 's'}');
|
if (errors > 0) parts.add(widget.strings.diagnosticErrors(errors));
|
||||||
if (warnings > 0) {
|
if (warnings > 0) {
|
||||||
parts.add('$warnings warning${warnings == 1 ? '' : 's'}');
|
parts.add(widget.strings.diagnosticWarnings(warnings));
|
||||||
}
|
}
|
||||||
return parts.join(' · ');
|
return parts.join(' · ');
|
||||||
}
|
}
|
||||||
|
|
@ -1487,6 +1527,7 @@ class _IssueRow extends StatelessWidget {
|
||||||
final Issue issue;
|
final Issue issue;
|
||||||
final List<QuickFix> fixes;
|
final List<QuickFix> fixes;
|
||||||
final Color tone;
|
final Color tone;
|
||||||
|
final FlowEditorStrings strings;
|
||||||
final Map<QuickFix, bool> busy;
|
final Map<QuickFix, bool> busy;
|
||||||
final Future<void> Function(QuickFix) onApplyFix;
|
final Future<void> Function(QuickFix) onApplyFix;
|
||||||
final VoidCallback onCopy;
|
final VoidCallback onCopy;
|
||||||
|
|
@ -1495,6 +1536,7 @@ class _IssueRow extends StatelessWidget {
|
||||||
required this.issue,
|
required this.issue,
|
||||||
required this.fixes,
|
required this.fixes,
|
||||||
required this.tone,
|
required this.tone,
|
||||||
|
required this.strings,
|
||||||
required this.busy,
|
required this.busy,
|
||||||
required this.onApplyFix,
|
required this.onApplyFix,
|
||||||
required this.onCopy,
|
required this.onCopy,
|
||||||
|
|
@ -1517,10 +1559,9 @@ class _IssueRow extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 42,
|
width: 42,
|
||||||
child: Text(
|
child: Text(
|
||||||
'L${issue.line + 1}',
|
strings.diagnosticLinePrefix(issue.line + 1),
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 11,
|
||||||
fontSize: 11,
|
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1528,16 +1569,15 @@ class _IssueRow extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
issue.message,
|
issue.message,
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 11,
|
||||||
fontSize: 11,
|
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Copy',
|
tooltip: strings.runCopy,
|
||||||
icon: const Icon(Icons.content_copy, size: 13),
|
icon: const Icon(Icons.content_copy, size: 13),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
|
@ -1557,10 +1597,7 @@ class _IssueRow extends StatelessWidget {
|
||||||
: const Icon(Icons.auto_fix_high, size: 13),
|
: const Icon(Icons.auto_fix_high, size: 13),
|
||||||
label: Text(
|
label: Text(
|
||||||
fix.label,
|
fix.label,
|
||||||
style: const TextStyle(
|
style: _monoTextStyle(size: 11),
|
||||||
fontFamily: 'JetBrains Mono',
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
|
|
@ -1586,12 +1623,14 @@ class _IssueRow extends StatelessWidget {
|
||||||
/// tooltip so action buttons stay clickable.
|
/// tooltip so action buttons stay clickable.
|
||||||
class _IssueHoverCard extends StatelessWidget {
|
class _IssueHoverCard extends StatelessWidget {
|
||||||
final IssueHoverRequest request;
|
final IssueHoverRequest request;
|
||||||
|
final FlowEditorStrings strings;
|
||||||
final Future<void> Function(QuickFix) onApplyFix;
|
final Future<void> Function(QuickFix) onApplyFix;
|
||||||
final VoidCallback onEnter;
|
final VoidCallback onEnter;
|
||||||
final VoidCallback onExit;
|
final VoidCallback onExit;
|
||||||
|
|
||||||
const _IssueHoverCard({
|
const _IssueHoverCard({
|
||||||
required this.request,
|
required this.request,
|
||||||
|
required this.strings,
|
||||||
required this.onApplyFix,
|
required this.onApplyFix,
|
||||||
required this.onEnter,
|
required this.onEnter,
|
||||||
required this.onExit,
|
required this.onExit,
|
||||||
|
|
@ -1666,10 +1705,9 @@ class _IssueHoverCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'L${request.line + 1}',
|
strings.diagnosticLinePrefix(request.line + 1),
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 11,
|
||||||
fontSize: 11,
|
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1677,22 +1715,21 @@ class _IssueHoverCard extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
request.message,
|
request.message,
|
||||||
style: TextStyle(
|
style: _monoTextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
size: 12,
|
||||||
fontSize: 12,
|
|
||||||
color: theme.colorScheme.onSurface,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Copy',
|
tooltip: strings.runCopy,
|
||||||
icon: const Icon(Icons.content_copy, size: 14),
|
icon: const Icon(Icons.content_copy, size: 14),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(
|
ClipboardData(
|
||||||
text:
|
text:
|
||||||
'L${request.line + 1}: ${request.message}',
|
'${strings.diagnosticLinePrefix(request.line + 1)}: ${request.message}',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1711,10 +1748,7 @@ class _IssueHoverCard extends StatelessWidget {
|
||||||
icon: const Icon(Icons.auto_fix_high, size: 13),
|
icon: const Icon(Icons.auto_fix_high, size: 13),
|
||||||
label: Text(
|
label: Text(
|
||||||
fix.label,
|
fix.label,
|
||||||
style: const TextStyle(
|
style: _monoTextStyle(size: 11),
|
||||||
fontFamily: 'JetBrains Mono',
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||||
import 'package:highlight/languages/yaml.dart';
|
import 'package:highlight/languages/yaml.dart';
|
||||||
|
|
||||||
import 'flow_analyzer.dart';
|
import 'flow_analyzer.dart';
|
||||||
|
import 'l10n.dart';
|
||||||
import 'quick_fix.dart';
|
import 'quick_fix.dart';
|
||||||
import 'wire_colors.dart';
|
import 'wire_colors.dart';
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ class FlowYamlCodeController extends CodeController {
|
||||||
FlowYamlCodeController({
|
FlowYamlCodeController({
|
||||||
List<String> Function()? availableCapabilities,
|
List<String> Function()? availableCapabilities,
|
||||||
List<String> Function()? storeCapabilities,
|
List<String> Function()? storeCapabilities,
|
||||||
|
AnalyzerStrings analyzerStrings = AnalyzerStrings.english,
|
||||||
}) : super(
|
}) : super(
|
||||||
text: '',
|
text: '',
|
||||||
language: yaml,
|
language: yaml,
|
||||||
|
|
@ -37,20 +39,23 @@ class FlowYamlCodeController extends CodeController {
|
||||||
availableCapabilities:
|
availableCapabilities:
|
||||||
availableCapabilities ?? (() => const <String>[]),
|
availableCapabilities ?? (() => const <String>[]),
|
||||||
storeCapabilities: storeCapabilities,
|
storeCapabilities: storeCapabilities,
|
||||||
|
strings: analyzerStrings,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Set both capability providers in one go. Re-creating the
|
/// Set capability providers AND the analyzer's locale-aware
|
||||||
/// analyzer is the simplest way to flip both closures without
|
/// message strings. Re-creating the analyzer is the simplest
|
||||||
/// risking a stale storeCapabilities reference inside an old
|
/// way to flip these atomically without risking a stale
|
||||||
/// FlowAnalyzer instance.
|
/// closure or stale strings inside an old FlowAnalyzer.
|
||||||
void setCapabilityProviders({
|
void setCapabilityProviders({
|
||||||
required List<String> Function() available,
|
required List<String> Function() available,
|
||||||
List<String> Function()? store,
|
List<String> Function()? store,
|
||||||
|
AnalyzerStrings? strings,
|
||||||
}) {
|
}) {
|
||||||
analyzer = FlowAnalyzer(
|
analyzer = FlowAnalyzer(
|
||||||
availableCapabilities: available,
|
availableCapabilities: available,
|
||||||
storeCapabilities: store,
|
storeCapabilities: store,
|
||||||
|
strings: strings ?? AnalyzerStrings.english,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,4 +145,161 @@ class FlowEditorStrings {
|
||||||
String get endpointExpression => _t('Expression', 'Ausdruck');
|
String get endpointExpression => _t('Expression', 'Ausdruck');
|
||||||
String get endpointAdd => _t('Add', 'Hinzufügen');
|
String get endpointAdd => _t('Add', 'Hinzufügen');
|
||||||
String get endpointRemove => _t('Remove', 'Entfernen');
|
String get endpointRemove => _t('Remove', 'Entfernen');
|
||||||
|
|
||||||
|
// Run-tab step row + error block.
|
||||||
|
String get runAwaitingApproval =>
|
||||||
|
_t('awaiting approval', 'wartet auf Freigabe');
|
||||||
|
String get runCopy => _t('Copy', 'Kopieren');
|
||||||
|
String get runCopied => _t('Copied', 'Kopiert');
|
||||||
|
String runErrorsBlockRun(int n) => _t(
|
||||||
|
'$n error${n == 1 ? '' : 's'} prevent the run',
|
||||||
|
'$n ${n == 1 ? 'Fehler verhindert' : 'Fehler verhindern'} den Lauf',
|
||||||
|
);
|
||||||
|
String get runErrorsBlockHint => _t(
|
||||||
|
'check the diagnostic strip',
|
||||||
|
'siehe Diagnose-Leiste unten',
|
||||||
|
);
|
||||||
|
String runErrorsBlockTooltip(int n) => _t(
|
||||||
|
'$n error${n == 1 ? '' : 's'} prevent the run — fix them first',
|
||||||
|
'$n ${n == 1 ? 'Fehler verhindert' : 'Fehler verhindern'} den Lauf — '
|
||||||
|
'bitte zuerst beheben',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom diagnostic strip + hover card.
|
||||||
|
String get diagnosticNoIssues => _t('No issues', 'Keine Probleme');
|
||||||
|
String diagnosticErrors(int n) =>
|
||||||
|
_t('$n error${n == 1 ? '' : 's'}', '$n Fehler');
|
||||||
|
String diagnosticWarnings(int n) =>
|
||||||
|
_t('$n warning${n == 1 ? '' : 's'}', '$n Warnungen');
|
||||||
|
String get diagnosticCopyAll => _t('Copy all', 'Alle kopieren');
|
||||||
|
String diagnosticLinePrefix(int line) => 'L$line';
|
||||||
|
|
||||||
|
// Analyzer-emitted messages — used by FlowAnalyzer via the
|
||||||
|
// [AnalyzerStrings] adapter below so the same strings can be
|
||||||
|
// localized centrally.
|
||||||
|
String analyzerUnknownCapInStore(String cap) => _t(
|
||||||
|
'Unknown capability "$cap". '
|
||||||
|
'Install via the Fix button or check the spelling.',
|
||||||
|
'Unbekannte Capability "$cap". '
|
||||||
|
'Per Fix-Button installieren oder Tippfehler prüfen.',
|
||||||
|
);
|
||||||
|
String analyzerUnknownCapTypo(String cap, String suggestion) => _t(
|
||||||
|
'Unknown capability "$cap". Did you mean "$suggestion"?',
|
||||||
|
'Unbekannte Capability "$cap". Meintest du "$suggestion"?',
|
||||||
|
);
|
||||||
|
String analyzerUnknownCapNotInStore(String cap) => _t(
|
||||||
|
'Unknown capability "$cap". '
|
||||||
|
'Not in the store — install locally with '
|
||||||
|
'`fai install --link <path>` or check the spelling.',
|
||||||
|
'Unbekannte Capability "$cap". '
|
||||||
|
'Nicht im Store — lokal mit '
|
||||||
|
'`fai install --link <pfad>` installieren oder Tippfehler prüfen.',
|
||||||
|
);
|
||||||
|
String analyzerInputKind() => _t('input', 'Eingabe');
|
||||||
|
String analyzerOutputKind() => _t('output', 'Ausgabe');
|
||||||
|
String analyzerUnknownType(String kind, String value, String validList) =>
|
||||||
|
_t(
|
||||||
|
'Unknown $kind type "$value". Use one of: $validList.',
|
||||||
|
'Unbekannter $kind-Typ "$value". Erlaubt sind: $validList.',
|
||||||
|
);
|
||||||
|
String analyzerYamlError(String detail) => _t('YAML: $detail', 'YAML: $detail');
|
||||||
|
|
||||||
|
// Quick-fix button labels.
|
||||||
|
String fixInstallCap(String cap) => _t('Install $cap', '$cap installieren');
|
||||||
|
String fixAddSource(String cap) =>
|
||||||
|
_t('Add source for $cap…', 'Quelle für $cap hinzufügen…');
|
||||||
|
String fixUseInstead(String name) =>
|
||||||
|
_t('Use "$name"', '"$name" verwenden');
|
||||||
|
String fixChangeTo(String name) =>
|
||||||
|
_t('Change to "$name"', 'Auf "$name" ändern');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset of [FlowEditorStrings] the analyzer consumes. Kept as
|
||||||
|
/// a separate adapter so a test or alternate host can plug in
|
||||||
|
/// a custom string-builder without owning the whole Editor
|
||||||
|
/// string table.
|
||||||
|
class AnalyzerStrings {
|
||||||
|
final String Function(String cap) unknownCapInStore;
|
||||||
|
final String Function(String cap, String suggestion) unknownCapTypo;
|
||||||
|
final String Function(String cap) unknownCapNotInStore;
|
||||||
|
final String Function(String kind, String value, String validList)
|
||||||
|
unknownType;
|
||||||
|
final String Function() inputKind;
|
||||||
|
final String Function() outputKind;
|
||||||
|
final String Function(String detail) yamlError;
|
||||||
|
|
||||||
|
final String Function(String cap) fixInstallCap;
|
||||||
|
final String Function(String cap) fixAddSource;
|
||||||
|
final String Function(String name) fixUseInstead;
|
||||||
|
final String Function(String name) fixChangeTo;
|
||||||
|
|
||||||
|
const AnalyzerStrings({
|
||||||
|
required this.unknownCapInStore,
|
||||||
|
required this.unknownCapTypo,
|
||||||
|
required this.unknownCapNotInStore,
|
||||||
|
required this.unknownType,
|
||||||
|
required this.inputKind,
|
||||||
|
required this.outputKind,
|
||||||
|
required this.yamlError,
|
||||||
|
required this.fixInstallCap,
|
||||||
|
required this.fixAddSource,
|
||||||
|
required this.fixUseInstead,
|
||||||
|
required this.fixChangeTo,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Build an [AnalyzerStrings] from the editor's
|
||||||
|
/// [FlowEditorStrings] — every host that uses the editor
|
||||||
|
/// gets analyzer messages in the same language as the rest
|
||||||
|
/// of the UI for free.
|
||||||
|
factory AnalyzerStrings.from(FlowEditorStrings s) => AnalyzerStrings(
|
||||||
|
unknownCapInStore: s.analyzerUnknownCapInStore,
|
||||||
|
unknownCapTypo: s.analyzerUnknownCapTypo,
|
||||||
|
unknownCapNotInStore: s.analyzerUnknownCapNotInStore,
|
||||||
|
unknownType: s.analyzerUnknownType,
|
||||||
|
inputKind: s.analyzerInputKind,
|
||||||
|
outputKind: s.analyzerOutputKind,
|
||||||
|
yamlError: s.analyzerYamlError,
|
||||||
|
fixInstallCap: s.fixInstallCap,
|
||||||
|
fixAddSource: s.fixAddSource,
|
||||||
|
fixUseInstead: s.fixUseInstead,
|
||||||
|
fixChangeTo: s.fixChangeTo,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Built-in English defaults — used by tests + by the
|
||||||
|
/// editor when no host has wired strings yet. Identical to
|
||||||
|
/// FlowEditorStrings(en) output so behaviour matches across
|
||||||
|
/// the two code paths.
|
||||||
|
static const AnalyzerStrings english = AnalyzerStrings._english();
|
||||||
|
|
||||||
|
const AnalyzerStrings._english()
|
||||||
|
: unknownCapInStore = _enUnknownCapInStore,
|
||||||
|
unknownCapTypo = _enUnknownCapTypo,
|
||||||
|
unknownCapNotInStore = _enUnknownCapNotInStore,
|
||||||
|
unknownType = _enUnknownType,
|
||||||
|
inputKind = _enInputKind,
|
||||||
|
outputKind = _enOutputKind,
|
||||||
|
yamlError = _enYamlError,
|
||||||
|
fixInstallCap = _enFixInstall,
|
||||||
|
fixAddSource = _enFixAddSource,
|
||||||
|
fixUseInstead = _enFixUse,
|
||||||
|
fixChangeTo = _enFixChange;
|
||||||
|
|
||||||
|
static String _enUnknownCapInStore(String cap) =>
|
||||||
|
'Unknown capability "$cap". '
|
||||||
|
'Install via the Fix button or check the spelling.';
|
||||||
|
static String _enUnknownCapTypo(String cap, String suggestion) =>
|
||||||
|
'Unknown capability "$cap". Did you mean "$suggestion"?';
|
||||||
|
static String _enUnknownCapNotInStore(String cap) =>
|
||||||
|
'Unknown capability "$cap". '
|
||||||
|
'Not in the store — install locally with '
|
||||||
|
'`fai install --link <path>` or check the spelling.';
|
||||||
|
static String _enUnknownType(String kind, String value, String validList) =>
|
||||||
|
'Unknown $kind type "$value". Use one of: $validList.';
|
||||||
|
static String _enInputKind() => 'input';
|
||||||
|
static String _enOutputKind() => 'output';
|
||||||
|
static String _enYamlError(String detail) => 'YAML: $detail';
|
||||||
|
static String _enFixInstall(String cap) => 'Install $cap';
|
||||||
|
static String _enFixAddSource(String cap) => 'Add source for $cap…';
|
||||||
|
static String _enFixUse(String name) => 'Use "$name"';
|
||||||
|
static String _enFixChange(String name) => 'Change to "$name"';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,10 @@ class _RunTabState extends State<RunTab> {
|
||||||
if (_error != null) ...[
|
if (_error != null) ...[
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: FaiSpace.lg),
|
||||||
_section(theme, strings.runTitleFailed),
|
_section(theme, strings.runTitleFailed),
|
||||||
_CopyableErrorBox(text: _error.toString()),
|
_CopyableErrorBox(
|
||||||
|
text: _error.toString(),
|
||||||
|
strings: widget.strings,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -300,6 +303,7 @@ class _RunTabState extends State<RunTab> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _stepRow(ThemeData theme, String id, _StepState state) {
|
Widget _stepRow(ThemeData theme, String id, _StepState state) {
|
||||||
|
final strings = widget.strings;
|
||||||
final IconData glyph;
|
final IconData glyph;
|
||||||
final Color color;
|
final Color color;
|
||||||
String suffix = '';
|
String suffix = '';
|
||||||
|
|
@ -317,7 +321,7 @@ class _RunTabState extends State<RunTab> {
|
||||||
case _StepKind.awaiting:
|
case _StepKind.awaiting:
|
||||||
glyph = Icons.pause_circle_outline;
|
glyph = Icons.pause_circle_outline;
|
||||||
color = theme.colorScheme.tertiary;
|
color = theme.colorScheme.tertiary;
|
||||||
suffix = ' awaiting approval';
|
suffix = ' ${strings.runAwaitingApproval}';
|
||||||
}
|
}
|
||||||
final isError = state.kind == _StepKind.error &&
|
final isError = state.kind == _StepKind.error &&
|
||||||
state.error != null &&
|
state.error != null &&
|
||||||
|
|
@ -347,7 +351,10 @@ class _RunTabState extends State<RunTab> {
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 24),
|
padding: const EdgeInsets.only(left: 24),
|
||||||
child: _CopyableErrorBox(text: state.error!),
|
child: _CopyableErrorBox(
|
||||||
|
text: state.error!,
|
||||||
|
strings: widget.strings,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
@ -365,9 +372,7 @@ class _RunTabState extends State<RunTab> {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: blocked
|
message: blocked ? strings.runErrorsBlockTooltip(errors) : '',
|
||||||
? '$errors error${errors == 1 ? '' : 's'} prevent the run'
|
|
||||||
: '',
|
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: _running || widget.driver == null || blocked
|
onPressed: _running || widget.driver == null || blocked
|
||||||
? null
|
? null
|
||||||
|
|
@ -389,8 +394,8 @@ class _RunTabState extends State<RunTab> {
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: FaiSpace.sm),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'$errors error${errors == 1 ? '' : 's'} prevent the run · '
|
'${strings.runErrorsBlockRun(errors)} · '
|
||||||
'check the diagnostic strip',
|
'${strings.runErrorsBlockHint}',
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
|
|
@ -619,7 +624,8 @@ class _StepState {
|
||||||
/// so flow_editor doesn't depend on Studio internals.
|
/// so flow_editor doesn't depend on Studio internals.
|
||||||
class _CopyableErrorBox extends StatefulWidget {
|
class _CopyableErrorBox extends StatefulWidget {
|
||||||
final String text;
|
final String text;
|
||||||
const _CopyableErrorBox({required this.text});
|
final FlowEditorStrings strings;
|
||||||
|
const _CopyableErrorBox({required this.text, required this.strings});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_CopyableErrorBox> createState() => _CopyableErrorBoxState();
|
State<_CopyableErrorBox> createState() => _CopyableErrorBoxState();
|
||||||
|
|
@ -661,7 +667,8 @@ class _CopyableErrorBoxState extends State<_CopyableErrorBox> {
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: _justCopied ? 'Copied' : 'Copy',
|
message:
|
||||||
|
_justCopied ? widget.strings.runCopied : widget.strings.runCopy,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_justCopied ? Icons.check : Icons.content_copy,
|
_justCopied ? Icons.check : Icons.content_copy,
|
||||||
|
|
@ -682,6 +689,12 @@ class _CopyableErrorBoxState extends State<_CopyableErrorBox> {
|
||||||
widget.text,
|
widget.text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'JetBrains Mono',
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontFamilyFallback: const [
|
||||||
|
'Menlo',
|
||||||
|
'Consolas',
|
||||||
|
'Courier New',
|
||||||
|
'monospace',
|
||||||
|
],
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: theme.colorScheme.onErrorContainer,
|
color: theme.colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: fai_studio_flow_editor
|
name: fai_studio_flow_editor
|
||||||
description: Swappable inline YAML editor for F∆I Studio flows.
|
description: Swappable inline YAML editor for F∆I Studio flows.
|
||||||
version: 0.19.0
|
version: 0.20.0
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
repository: https://git.flemming.ai/fai/studio-flow-editor
|
repository: https://git.flemming.ai/fai/studio-flow-editor
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue