fix: dirty detection — compare fullText, snapshot after load
Stefan reported false-positive "Discard unsaved changes?"
dialogs when clicking around in the file list. Cause: the
naive `_code.text == _loadedText` check fired stale even
right after _code.text = text, because CodeController
silently rewrites the visible text during the assignment
(trailing-newline normalisation, fold marker insertion on
some YAML constructs, …) so _code.text deviated from the
just-stored _loadedText immediately.
Fix:
* Drop `_loadedText` field, replace with `_baseline`.
* Set `_code.text = ...` first (outside setState), then
capture `_baseline = _code.fullText` inside setState.
fullText is the canonical post-processed value, not the
visible-after-folding text — comparing fullText against
fullText eliminates the spurious diff.
* _dirty getter now compares `_code.fullText != _baseline`.
Applied to all four load / save / new-flow sites.
Bump version 0.1.0 → 0.1.1. Studio's pubspec already pins
`ref: main` so the next `flutter pub get` picks the fix up.
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
51f9a1d2b1
commit
8b918f8f2a
2 changed files with 22 additions and 10 deletions
|
|
@ -52,7 +52,14 @@ class _FlowEditorPageState extends State<FlowEditorPage> {
|
||||||
late final CodeController _code;
|
late final CodeController _code;
|
||||||
late final FlowEditorStrings _l;
|
late final FlowEditorStrings _l;
|
||||||
String? _activeName;
|
String? _activeName;
|
||||||
String _loadedText = '';
|
// Baseline captured AFTER the CodeController has finished
|
||||||
|
// processing the loaded text (line-ending normalisation,
|
||||||
|
// trailing-newline handling, fold-marker insertion, …).
|
||||||
|
// Comparing _code.fullText against this baseline avoids the
|
||||||
|
// false-positive "discard unsaved changes?" prompts the
|
||||||
|
// naive _code.text == _loadedText check used to trigger on
|
||||||
|
// files the controller silently rewrote during load.
|
||||||
|
String _baseline = '';
|
||||||
late Future<List<_FlowFile>> _files;
|
late Future<List<_FlowFile>> _files;
|
||||||
Object? _runError;
|
Object? _runError;
|
||||||
Map<String, Object>? _runOutputs;
|
Map<String, Object>? _runOutputs;
|
||||||
|
|
@ -85,7 +92,7 @@ class _FlowEditorPageState extends State<FlowEditorPage> {
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _dirty => _activeName != null && _code.text != _loadedText;
|
bool get _dirty => _activeName != null && _code.fullText != _baseline;
|
||||||
|
|
||||||
Future<List<_FlowFile>> _listFiles() async {
|
Future<List<_FlowFile>> _listFiles() async {
|
||||||
final dir = Directory(_defaultFlowsDir());
|
final dir = Directory(_defaultFlowsDir());
|
||||||
|
|
@ -115,10 +122,15 @@ class _FlowEditorPageState extends State<FlowEditorPage> {
|
||||||
if (!f.existsSync()) return;
|
if (!f.existsSync()) return;
|
||||||
final text = await f.readAsString();
|
final text = await f.readAsString();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
// Set the controller text first, then snapshot fullText
|
||||||
|
// as the baseline. Reading fullText after the assignment
|
||||||
|
// captures whatever line-ending normalisation / fold-
|
||||||
|
// marker insertion CodeController applied, so the dirty
|
||||||
|
// check is comparing apples to apples.
|
||||||
|
_code.text = text;
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeName = name;
|
_activeName = name;
|
||||||
_loadedText = text;
|
_baseline = _code.fullText;
|
||||||
_code.text = text;
|
|
||||||
_runOutputs = null;
|
_runOutputs = null;
|
||||||
_runError = null;
|
_runError = null;
|
||||||
});
|
});
|
||||||
|
|
@ -130,10 +142,10 @@ class _FlowEditorPageState extends State<FlowEditorPage> {
|
||||||
if (keep == false || !mounted) return;
|
if (keep == false || !mounted) return;
|
||||||
}
|
}
|
||||||
final text = await File(f.path).readAsString();
|
final text = await File(f.path).readAsString();
|
||||||
|
_code.text = text;
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeName = f.name;
|
_activeName = f.name;
|
||||||
_loadedText = text;
|
_baseline = _code.fullText;
|
||||||
_code.text = text;
|
|
||||||
_runOutputs = null;
|
_runOutputs = null;
|
||||||
_runError = null;
|
_runError = null;
|
||||||
});
|
});
|
||||||
|
|
@ -168,7 +180,7 @@ class _FlowEditorPageState extends State<FlowEditorPage> {
|
||||||
await f.writeAsString(_code.text, flush: true);
|
await f.writeAsString(_code.text, flush: true);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_loadedText = _code.text;
|
_baseline = _code.fullText;
|
||||||
_files = _listFiles();
|
_files = _listFiles();
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -215,10 +227,10 @@ outputs:
|
||||||
}
|
}
|
||||||
await f.writeAsString(template, flush: true);
|
await f.writeAsString(template, flush: true);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_code.text = template;
|
||||||
setState(() {
|
setState(() {
|
||||||
_activeName = name;
|
_activeName = name;
|
||||||
_loadedText = template;
|
_baseline = _code.fullText;
|
||||||
_code.text = template;
|
|
||||||
_files = _listFiles();
|
_files = _listFiles();
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -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.1.0
|
version: 0.1.1
|
||||||
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