Filter the file list by the host's active project — flows without a project: key count as 'general', display-only, the file is never rewritten. New flows are stamped with the active project's key (general and all-projects stay unstamped). Adds a toolbarTrailing slot so the host can mount its workspace switcher in the editor's single toolbar, and a flowsDir injection point so widget tests run against a temp dir instead of the operator's ~/.chain flows. Covered by pure filter-semantics tests plus hermetic widget tests for filtering, the project-empty state, and new-flow stamping. Signed-off-by: flemming-it <sf@flemming.it>
65 lines
2.4 KiB
Dart
65 lines
2.4 KiB
Dart
/// Parsing + normalization for a flow file's own top-level
|
|
/// `project:` key. Kept separate from the page so the "file wins"
|
|
/// semantics are unit-testable without pumping the whole editor.
|
|
///
|
|
/// Mirrors the hub's `resolve_project` (file's `project:` is the
|
|
/// run's project unless the caller overrides it) and
|
|
/// `normalize_project_slug`, so the chip the editor shows compares
|
|
/// equal to what the hub actually stamps.
|
|
library;
|
|
|
|
/// The flow's own top-level `project:` slug (normalized), or empty
|
|
/// when the YAML declares none. Only a *non-indented* `project:`
|
|
/// line counts — a nested `project:` inside a step's `with:` block
|
|
/// is ignored.
|
|
String parseFlowProject(String yaml) {
|
|
for (final raw in yaml.split('\n')) {
|
|
if (raw.startsWith(' ') || raw.startsWith('\t')) continue;
|
|
final m = RegExp(r'^project:\s*(.+?)\s*$').firstMatch(raw);
|
|
if (m != null) {
|
|
final value = m.group(1)!.replaceAll(RegExp('''^["']|["']\$'''), '');
|
|
return normalizeProjectSlug(value);
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/// The project a flow file effectively belongs to: its own
|
|
/// `project:` slug, or `general` when the file declares none.
|
|
/// Display/filter semantics only — the FILE stays the truth and
|
|
/// is never rewritten to make this explicit.
|
|
String effectiveFlowProject(String fileProject) =>
|
|
fileProject.isEmpty ? 'general' : fileProject;
|
|
|
|
/// Whether a flow file belongs in the list under the active
|
|
/// workspace filter. Empty [activeProject] = "all projects";
|
|
/// otherwise the file's effective project (no key = `general`)
|
|
/// must match.
|
|
bool flowVisibleInProject(String fileProject, String activeProject) =>
|
|
activeProject.isEmpty ||
|
|
effectiveFlowProject(fileProject) == activeProject;
|
|
|
|
/// Mirror of the hub's `normalize_project_slug`: lowercase,
|
|
/// collapse spaces/underscores/dashes to a single `-`, drop
|
|
/// everything else, trim trailing dashes. Empty in → empty out
|
|
/// (the editor treats "no project" and "blank project" alike).
|
|
String normalizeProjectSlug(String raw) {
|
|
final buf = StringBuffer();
|
|
var prevDash = false;
|
|
for (final ch in raw.trim().toLowerCase().split('')) {
|
|
if (RegExp(r'[a-z0-9]').hasMatch(ch)) {
|
|
buf.write(ch);
|
|
prevDash = false;
|
|
} else if ((ch == ' ' || ch == '_' || ch == '-') &&
|
|
!prevDash &&
|
|
buf.isNotEmpty) {
|
|
buf.write('-');
|
|
prevDash = true;
|
|
}
|
|
}
|
|
var out = buf.toString();
|
|
while (out.endsWith('-')) {
|
|
out = out.substring(0, out.length - 1);
|
|
}
|
|
return out;
|
|
}
|