feat: usertest fixes — hub-truth builtins, calm badge + separate install action, list filter, formal address
- drop the hardcoded builtin set {'debug.echo'}; missing-module
detection trusts the host capability list (includes real hub
builtins) — fixes hello's false play button
- split the orange combined chip into a neutral status chip and a
never-truncated install link
- add a substring filter over the flow list with an honest
no-match state
- German strings use the formal address throughout; grammar fixes
- analyzer hint says 'chain install --link' (was 'fai …')
- 0.24.0
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
ae09a55146
commit
4e7abec24d
4 changed files with 199 additions and 80 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -1,5 +1,27 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.24.0
|
||||||
|
|
||||||
|
Usertest-panel fixes (flow list + language):
|
||||||
|
|
||||||
|
- **Builtin-capability truth from the hub.** Removed the hardcoded
|
||||||
|
client-side builtin set (`{'debug.echo'}`): missing-module
|
||||||
|
detection now trusts the host-supplied capability list alone,
|
||||||
|
which already includes the hub's real builtins. Fixes the bundled
|
||||||
|
`hello` flow showing a play button on hubs that don't have
|
||||||
|
`debug.echo` installed.
|
||||||
|
- **Missing-modules badge redesigned.** Status ("N Module fehlen",
|
||||||
|
quiet neutral chip with an amber dot) and the install action
|
||||||
|
(own link, never truncated) are separate elements; the former
|
||||||
|
combined orange chip read as an error wall and ellipsized the
|
||||||
|
action word.
|
||||||
|
- **Flow-list filter.** Case-insensitive substring filter above the
|
||||||
|
list, with an honest "no flow matches" empty state.
|
||||||
|
- **Formal address (Sie) in all German strings** plus missing
|
||||||
|
commas/quotes in the empty-state sentences.
|
||||||
|
- Analyzer hint now names the current CLI (`chain install --link`,
|
||||||
|
was `fai install --link`).
|
||||||
|
|
||||||
## 0.23.0
|
## 0.23.0
|
||||||
|
|
||||||
- Run tab file inputs accept a host-injected native file picker
|
- Run tab file inputs accept a host-injected native file picker
|
||||||
|
|
|
||||||
|
|
@ -1252,11 +1252,6 @@ class _FlowFile {
|
||||||
/// content contains this exact string.
|
/// content contains this exact string.
|
||||||
const String _sampleFlowMarker = 'F∆I sample flow';
|
const String _sampleFlowMarker = 'F∆I sample flow';
|
||||||
|
|
||||||
/// Built-in capabilities the Hub always provides, even when
|
|
||||||
/// they aren't in the host-supplied installed list. Treated as
|
|
||||||
/// satisfied so the "needs N modules" badge never flags them.
|
|
||||||
const Set<String> _builtinCapabilities = {'debug.echo'};
|
|
||||||
|
|
||||||
/// Result of scanning a single flow file: provenance + the
|
/// Result of scanning a single flow file: provenance + the
|
||||||
/// capability NAMES (without `@version`) its steps reference.
|
/// capability NAMES (without `@version`) its steps reference.
|
||||||
class _FlowMeta {
|
class _FlowMeta {
|
||||||
|
|
@ -1266,15 +1261,16 @@ class _FlowMeta {
|
||||||
|
|
||||||
static const empty = _FlowMeta(isExample: false, requiredCaps: []);
|
static const empty = _FlowMeta(isExample: false, requiredCaps: []);
|
||||||
|
|
||||||
/// Capabilities this flow needs that are neither installed nor
|
/// Capabilities this flow needs that the hub does not provide.
|
||||||
/// built in. [availableNames] is the set of installed
|
/// [availableNames] is the set of capability NAMES (the part
|
||||||
/// capability NAMES (the part before `@`).
|
/// before `@`) from the host's live capability list — which
|
||||||
List<String> missingCaps(Set<String> availableNames) => requiredCaps
|
/// already includes the hub's builtins (e.g. system.approval),
|
||||||
.where(
|
/// so there is no client-side builtin list to drift out of
|
||||||
(c) =>
|
/// sync with the hub (the old hardcoded {'debug.echo'} made
|
||||||
!availableNames.contains(c) && !_builtinCapabilities.contains(c),
|
/// the bundled hello flow look runnable on hubs that don't
|
||||||
)
|
/// have the module).
|
||||||
.toList();
|
List<String> missingCaps(Set<String> availableNames) =>
|
||||||
|
requiredCaps.where((c) => !availableNames.contains(c)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caches [_FlowMeta] per file, keyed by path + mtime. Reading
|
/// Caches [_FlowMeta] per file, keyed by path + mtime. Reading
|
||||||
|
|
@ -1345,7 +1341,7 @@ String _formatBytes(int bytes) {
|
||||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FileList extends StatelessWidget {
|
class _FileList extends StatefulWidget {
|
||||||
final Future<List<_FlowFile>> filesFuture;
|
final Future<List<_FlowFile>> filesFuture;
|
||||||
final String? activeName;
|
final String? activeName;
|
||||||
final FlowEditorStrings strings;
|
final FlowEditorStrings strings;
|
||||||
|
|
@ -1377,9 +1373,20 @@ class _FileList extends StatelessWidget {
|
||||||
required this.onInstallMissing,
|
required this.onInstallMissing,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FileList> createState() => _FileListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FileListState extends State<_FileList> {
|
||||||
|
/// Case-insensitive substring filter over flow names. First
|
||||||
|
/// iteration of "find a flow fast" (usertest power-user
|
||||||
|
/// finding: no search over the flow list at all).
|
||||||
|
String _filter = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final strings = widget.strings;
|
||||||
return Container(
|
return Container(
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -1405,7 +1412,7 @@ class _FileList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: onRefresh,
|
onPressed: widget.onRefresh,
|
||||||
tooltip: strings.refresh,
|
tooltip: strings.refresh,
|
||||||
icon: const Icon(Icons.refresh, size: 16),
|
icon: const Icon(Icons.refresh, size: 16),
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
|
|
@ -1418,6 +1425,43 @@ class _FileList extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
FaiSpace.md,
|
||||||
|
FaiSpace.xs,
|
||||||
|
FaiSpace.md,
|
||||||
|
FaiSpace.xs,
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 28,
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (v) => setState(() => _filter = v.trim()),
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(Icons.search, size: 14),
|
||||||
|
prefixIconConstraints: const BoxConstraints(
|
||||||
|
minWidth: 28,
|
||||||
|
minHeight: 28,
|
||||||
|
),
|
||||||
|
hintText: strings.listFilterHint,
|
||||||
|
hintStyle: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: theme.colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(child: _buildBody(context, theme)),
|
Expanded(child: _buildBody(context, theme)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -1425,8 +1469,9 @@ class _FileList extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody(BuildContext context, ThemeData theme) {
|
Widget _buildBody(BuildContext context, ThemeData theme) {
|
||||||
|
final strings = widget.strings;
|
||||||
return FutureBuilder<List<_FlowFile>>(
|
return FutureBuilder<List<_FlowFile>>(
|
||||||
future: filesFuture,
|
future: widget.filesFuture,
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.connectionState == ConnectionState.waiting) {
|
if (snap.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|
@ -1437,8 +1482,8 @@ class _FileList extends StatelessWidget {
|
||||||
child: ChainErrorBox(error: snap.error, isError: true),
|
child: ChainErrorBox(error: snap.error, isError: true),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final files = snap.data ?? <_FlowFile>[];
|
final all = snap.data ?? <_FlowFile>[];
|
||||||
if (files.isEmpty) {
|
if (all.isEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(FaiSpace.md),
|
padding: const EdgeInsets.all(FaiSpace.md),
|
||||||
child: FaiEmptyState(
|
child: FaiEmptyState(
|
||||||
|
|
@ -1448,15 +1493,33 @@ class _FileList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final needle = _filter.toLowerCase();
|
||||||
|
final files = needle.isEmpty
|
||||||
|
? all
|
||||||
|
: all
|
||||||
|
.where((f) => f.name.toLowerCase().contains(needle))
|
||||||
|
.toList();
|
||||||
|
if (files.isEmpty) {
|
||||||
|
// Flows exist, the filter just matches none — say that
|
||||||
|
// instead of pretending the directory is empty.
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(FaiSpace.md),
|
||||||
|
child: FaiEmptyState(
|
||||||
|
icon: Icons.search_off_outlined,
|
||||||
|
title: strings.listFilterNoMatch(_filter),
|
||||||
|
hint: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.symmetric(vertical: FaiSpace.xs),
|
padding: const EdgeInsets.symmetric(vertical: FaiSpace.xs),
|
||||||
itemCount: files.length,
|
itemCount: files.length,
|
||||||
itemBuilder: (_, i) {
|
itemBuilder: (_, i) {
|
||||||
final f = files[i];
|
final f = files[i];
|
||||||
final isActive = f.name == activeName;
|
final isActive = f.name == widget.activeName;
|
||||||
final missing = f.meta.missingCaps(installedNames);
|
final missing = f.meta.missingCaps(widget.installedNames);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => onOpen(f),
|
onTap: () => widget.onOpen(f),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: FaiSpace.md,
|
horizontal: FaiSpace.md,
|
||||||
|
|
@ -1535,9 +1598,10 @@ class _FileList extends StatelessWidget {
|
||||||
_MissingModulesBadge(
|
_MissingModulesBadge(
|
||||||
missing: missing,
|
missing: missing,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
onInstall: onInstallMissing == null
|
onInstall: widget.onInstallMissing == null
|
||||||
? null
|
? null
|
||||||
: () => onInstallMissing!(missing),
|
: () =>
|
||||||
|
widget.onInstallMissing!(missing),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -1550,7 +1614,7 @@ class _FileList extends StatelessWidget {
|
||||||
// the install badge is the actionable path then.
|
// the install badge is the actionable path then.
|
||||||
if (missing.isEmpty)
|
if (missing.isEmpty)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => onStart(f),
|
onPressed: () => widget.onStart(f),
|
||||||
tooltip: strings.listStartTooltip,
|
tooltip: strings.listStartTooltip,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.play_arrow_rounded,
|
Icons.play_arrow_rounded,
|
||||||
|
|
@ -1617,11 +1681,17 @@ class _ExampleBadge extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Warning-toned "needs N modules" chip on flows whose steps
|
/// "N modules missing" status on flows whose steps reference
|
||||||
/// reference capabilities that aren't installed. Tooltip lists
|
/// capabilities the hub doesn't provide. Status and action are
|
||||||
/// the exact missing ids. When an install handler is wired, the
|
/// SEPARATE elements (usertest: the combined orange chip made
|
||||||
/// chip is tappable and one-click installs all missing caps via
|
/// the whole list read like an error wall and ellipsized the
|
||||||
/// the host's existing install path.
|
/// action word):
|
||||||
|
/// - status = quiet neutral chip with a small amber dot — a
|
||||||
|
/// note, not an alarm; warning orange stays reserved for real
|
||||||
|
/// failures.
|
||||||
|
/// - action = its own "Install" link that is never truncated;
|
||||||
|
/// both live in the row's Wrap, so tight widths wrap to a
|
||||||
|
/// second line instead of cutting text mid-word.
|
||||||
class _MissingModulesBadge extends StatelessWidget {
|
class _MissingModulesBadge extends StatelessWidget {
|
||||||
final List<String> missing;
|
final List<String> missing;
|
||||||
final FlowEditorStrings strings;
|
final FlowEditorStrings strings;
|
||||||
|
|
@ -1635,50 +1705,67 @@ class _MissingModulesBadge extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
// Amber warning tone consistent with the diagnostic strip's
|
const dot = Color(0xFFEF6C00);
|
||||||
// warning colour, readable on both light + dark surfaces.
|
final fg = theme.colorScheme.onSurfaceVariant;
|
||||||
const warn = Color(0xFFEF6C00);
|
final chip = Tooltip(
|
||||||
final label = '${strings.flowListNeedsModules(missing.length)}'
|
message: strings.flowListNeedsModulesTooltip(missing.join('\n')),
|
||||||
'${onInstall != null ? ' · ${strings.flowListInstallMissing}' : ''}';
|
child: Container(
|
||||||
final chip = Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: warn.withValues(alpha: 0.12),
|
color: theme.colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
||||||
border: Border.all(color: warn.withValues(alpha: 0.5)),
|
border: Border.all(
|
||||||
|
color: theme.colorScheme.outlineVariant.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.extension_off_outlined, size: 10, color: warn),
|
Container(
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: dot,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
// Flexible + ellipsis: the chip sits in the narrow flow
|
Text(
|
||||||
// list, where the full label otherwise overflows by a few
|
strings.flowListNeedsModules(missing.length),
|
||||||
// pixels (the tooltip still carries the complete text).
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
color: warn,
|
color: fg,
|
||||||
|
fontSize: 10,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (onInstall == null) return chip;
|
||||||
|
return Wrap(
|
||||||
|
spacing: FaiSpace.xs,
|
||||||
|
runSpacing: FaiSpace.xs,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
chip,
|
||||||
|
InkWell(
|
||||||
|
onTap: onInstall,
|
||||||
|
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||||
|
child: Text(
|
||||||
|
strings.flowListInstallMissing,
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
letterSpacing: 0.2,
|
letterSpacing: 0.2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
|
||||||
return Tooltip(
|
|
||||||
message: strings.flowListNeedsModulesTooltip(missing.join('\n')),
|
|
||||||
child: onInstall == null
|
|
||||||
? chip
|
|
||||||
: InkWell(
|
|
||||||
onTap: onInstall,
|
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
|
||||||
child: chip,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,14 @@ class FlowEditorStrings {
|
||||||
String get emptyTitle => _t('No flow open', 'Kein Flow geöffnet');
|
String get emptyTitle => _t('No flow open', 'Kein Flow geöffnet');
|
||||||
String get emptyBody => _t(
|
String get emptyBody => _t(
|
||||||
'Pick one from the left, or click New flow to start a fresh one.',
|
'Pick one from the left, or click New flow to start a fresh one.',
|
||||||
'Wähle links einen aus oder klicke Neuer Flow um einen neuen anzulegen.',
|
'Wählen Sie links einen Flow aus oder klicken Sie auf „Neuer Flow", '
|
||||||
|
'um einen neuen anzulegen.',
|
||||||
);
|
);
|
||||||
String get listEmptyTitle =>
|
String get listEmptyTitle =>
|
||||||
_t('No saved flows', 'Keine gespeicherten Flows');
|
_t('No saved flows', 'Keine gespeicherten Flows');
|
||||||
String get listEmptyBody => _t(
|
String get listEmptyBody => _t(
|
||||||
'Click New flow to scaffold one from a template.',
|
'Click New flow to scaffold one from a template.',
|
||||||
'Klicke Neuer Flow um aus einer Vorlage zu starten.',
|
'Klicken Sie auf „Neuer Flow", um aus einer Vorlage zu starten.',
|
||||||
);
|
);
|
||||||
String get discardTitle =>
|
String get discardTitle =>
|
||||||
_t('Discard unsaved changes?', 'Ungespeicherte Änderungen verwerfen?');
|
_t('Discard unsaved changes?', 'Ungespeicherte Änderungen verwerfen?');
|
||||||
|
|
@ -95,14 +96,15 @@ class FlowEditorStrings {
|
||||||
String get graphEmptyTitle => _t('No steps yet', 'Noch keine Schritte');
|
String get graphEmptyTitle => _t('No steps yet', 'Noch keine Schritte');
|
||||||
String get graphEmptyBody => _t(
|
String get graphEmptyBody => _t(
|
||||||
'Click Add step to drop the first capability onto the canvas.',
|
'Click Add step to drop the first capability onto the canvas.',
|
||||||
'Klicke Schritt hinzufügen um die erste Capability auf die Fläche zu setzen.',
|
'Klicken Sie auf „Schritt hinzufügen", um die erste Capability auf '
|
||||||
|
'die Fläche zu setzen.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Properties panel.
|
// Properties panel.
|
||||||
String get propPanelTitle => _t('Step details', 'Schritt-Details');
|
String get propPanelTitle => _t('Step details', 'Schritt-Details');
|
||||||
String get propNoSelection => _t(
|
String get propNoSelection => _t(
|
||||||
'Click a step on the canvas to edit it.',
|
'Click a step on the canvas to edit it.',
|
||||||
'Klicke einen Schritt auf der Fläche an, um ihn zu bearbeiten.',
|
'Klicken Sie einen Schritt auf der Fläche an, um ihn zu bearbeiten.',
|
||||||
);
|
);
|
||||||
String get propStepId => _t('ID', 'ID');
|
String get propStepId => _t('ID', 'ID');
|
||||||
String get propCapability => _t('Capability', 'Capability');
|
String get propCapability => _t('Capability', 'Capability');
|
||||||
|
|
@ -142,11 +144,12 @@ class FlowEditorStrings {
|
||||||
String get runStart => _t('Start run', 'Lauf starten');
|
String get runStart => _t('Start run', 'Lauf starten');
|
||||||
String get runUnsavedBanner => _t(
|
String get runUnsavedBanner => _t(
|
||||||
'You have unsaved changes — save first to run the latest version.',
|
'You have unsaved changes — save first to run the latest version.',
|
||||||
'Du hast ungespeicherte Änderungen — bitte zuerst speichern, um die neueste Version auszuführen.',
|
'Sie haben ungespeicherte Änderungen — bitte zuerst speichern, um die '
|
||||||
|
'neueste Version auszuführen.',
|
||||||
);
|
);
|
||||||
String get runNoFlow => _t(
|
String get runNoFlow => _t(
|
||||||
'Open a flow from the left to run it.',
|
'Open a flow from the left to run it.',
|
||||||
'Öffne links einen Flow um ihn auszuführen.',
|
'Öffnen Sie links einen Flow, um ihn auszuführen.',
|
||||||
);
|
);
|
||||||
String get runOutputs => _t('Outputs', 'Ausgaben');
|
String get runOutputs => _t('Outputs', 'Ausgaben');
|
||||||
String get runChooseFile => _t('Choose file…', 'Datei wählen…');
|
String get runChooseFile => _t('Choose file…', 'Datei wählen…');
|
||||||
|
|
@ -253,15 +256,15 @@ class FlowEditorStrings {
|
||||||
);
|
);
|
||||||
String analyzerUnknownCapTypo(String cap, String suggestion) => _t(
|
String analyzerUnknownCapTypo(String cap, String suggestion) => _t(
|
||||||
'Unknown capability "$cap". Did you mean "$suggestion"?',
|
'Unknown capability "$cap". Did you mean "$suggestion"?',
|
||||||
'Unbekannte Capability "$cap". Meintest du "$suggestion"?',
|
'Unbekannte Capability "$cap". Meinten Sie "$suggestion"?',
|
||||||
);
|
);
|
||||||
String analyzerUnknownCapNotInStore(String cap) => _t(
|
String analyzerUnknownCapNotInStore(String cap) => _t(
|
||||||
'Unknown capability "$cap". '
|
'Unknown capability "$cap". '
|
||||||
'Not in the store — install locally with '
|
'Not in the store — install locally with '
|
||||||
'`fai install --link <path>` or check the spelling.',
|
'`chain install --link <path>` or check the spelling.',
|
||||||
'Unbekannte Capability "$cap". '
|
'Unbekannte Capability "$cap". '
|
||||||
'Nicht im Store — lokal mit '
|
'Nicht im Store — lokal mit '
|
||||||
'`fai install --link <pfad>` installieren oder Tippfehler prüfen.',
|
'`chain install --link <pfad>` installieren oder Tippfehler prüfen.',
|
||||||
);
|
);
|
||||||
String analyzerInputKind() => _t('input', 'Eingabe');
|
String analyzerInputKind() => _t('input', 'Eingabe');
|
||||||
String analyzerOutputKind() => _t('output', 'Ausgabe');
|
String analyzerOutputKind() => _t('output', 'Ausgabe');
|
||||||
|
|
@ -280,8 +283,8 @@ class FlowEditorStrings {
|
||||||
'Mitgelieferter Beispiel-Flow.',
|
'Mitgelieferter Beispiel-Flow.',
|
||||||
);
|
);
|
||||||
String flowListNeedsModules(int n) => _t(
|
String flowListNeedsModules(int n) => _t(
|
||||||
n == 1 ? 'needs 1 module' : 'needs $n modules',
|
n == 1 ? '1 module missing' : '$n modules missing',
|
||||||
n == 1 ? 'braucht 1 Modul' : 'braucht $n Module',
|
n == 1 ? '1 Modul fehlt' : '$n Module fehlen',
|
||||||
);
|
);
|
||||||
String flowListNeedsModulesTooltip(String caps) => _t(
|
String flowListNeedsModulesTooltip(String caps) => _t(
|
||||||
'This flow needs capabilities that are not installed:\n$caps\n'
|
'This flow needs capabilities that are not installed:\n$caps\n'
|
||||||
|
|
@ -291,6 +294,13 @@ class FlowEditorStrings {
|
||||||
);
|
);
|
||||||
String get flowListInstallMissing => _t('Install', 'Installieren');
|
String get flowListInstallMissing => _t('Install', 'Installieren');
|
||||||
|
|
||||||
|
// Flow-list filter (first iteration: plain substring match).
|
||||||
|
String get listFilterHint => _t('Filter flows…', 'Flows filtern…');
|
||||||
|
String listFilterNoMatch(String query) => _t(
|
||||||
|
'No flow matches "$query".',
|
||||||
|
'Kein Flow passt zu „$query".',
|
||||||
|
);
|
||||||
|
|
||||||
// Quick-fix button labels.
|
// Quick-fix button labels.
|
||||||
String fixInstallCap(String cap) => _t('Install $cap', '$cap installieren');
|
String fixInstallCap(String cap) => _t('Install $cap', '$cap installieren');
|
||||||
String fixAddSource(String cap) =>
|
String fixAddSource(String cap) =>
|
||||||
|
|
@ -379,7 +389,7 @@ class AnalyzerStrings {
|
||||||
static String _enUnknownCapNotInStore(String cap) =>
|
static String _enUnknownCapNotInStore(String cap) =>
|
||||||
'Unknown capability "$cap". '
|
'Unknown capability "$cap". '
|
||||||
'Not in the store — install locally with '
|
'Not in the store — install locally with '
|
||||||
'`fai install --link <path>` or check the spelling.';
|
'`chain install --link <path>` or check the spelling.';
|
||||||
static String _enUnknownType(String kind, String value, String validList) =>
|
static String _enUnknownType(String kind, String value, String validList) =>
|
||||||
'Unknown $kind type "$value". Use one of: $validList.';
|
'Unknown $kind type "$value". Use one of: $validList.';
|
||||||
static String _enInputKind() => 'input';
|
static String _enInputKind() => 'input';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: chain_studio_flow_editor
|
name: chain_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.23.0
|
version: 0.24.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