Skip to content

Commit 55c604b

Browse files
committed
Fix embed/web loading errors, closes #1167
1 parent 1fca044 commit 55c604b

5 files changed

Lines changed: 143 additions & 55 deletions

File tree

app/lib/api/file_system.dart

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -208,32 +208,38 @@ class ButterflyFileSystem {
208208

209209
static const _database = 'butterfly.db';
210210
static const _databaseVersion = 5;
211-
212211
static Future<void> _upgradeDatabase(VersionChangeEvent event) async {
213212
final db = event.database;
214213
if (event.oldVersion < 1) {
215214
db.createObjectStore('documents');
216215
}
216+
if (event.oldVersion < 3) {
217+
db.createObjectStore('templates');
218+
}
219+
if (event.oldVersion < 4) {
220+
db.createObjectStore('packs');
221+
db.createObjectStore('documents-data');
222+
}
223+
if (event.oldVersion < 5) {
224+
db.createObjectStore('documentstates');
225+
db.createObjectStore('documentstates-data');
226+
}
217227
if (event.oldVersion < 2) {
218-
var txn = event.transaction;
219-
var store = txn.objectStore('documents');
220-
var cursor = store.openCursor();
228+
final txn = event.transaction;
229+
final store = txn.objectStore('documents');
230+
final cursor = store.openCursor();
231+
221232
await Future.wait(
222233
await cursor.map<Future<dynamic>>((cursor) async {
223-
// Add type to each document
224-
var doc = cursor.value as Map<dynamic, dynamic>;
234+
final doc = cursor.value as Map<dynamic, dynamic>;
225235
doc['type'] = 'document';
226236
await store.put(doc);
227237
}).toList(),
228238
);
229239
}
230-
if (event.oldVersion < 3) {
231-
db.createObjectStore('templates');
232-
}
240+
233241
if (event.oldVersion < 4) {
234-
db.createObjectStore('packs');
235-
db.createObjectStore('documents-data');
236-
var txn = event.transaction;
242+
final txn = event.transaction;
237243
var store = txn.objectStore('templates');
238244
var cursor = store.openCursor();
239245
await Future.wait(
@@ -261,10 +267,6 @@ class ButterflyFileSystem {
261267
);
262268
await txn.completed;
263269
}
264-
if (event.oldVersion < 5) {
265-
db.createObjectStore('documentstates');
266-
db.createObjectStore('documentstates-data');
267-
}
268270
}
269271

270272
String _cacheKey(ExternalStorage? storage) => storage?.identifier ?? 'local';

app/lib/repositories/document_state.dart

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,36 @@ class DocumentStateRepository {
2424
String? contentHash,
2525
String? pathKey,
2626
bool allowContentHash = true,
27-
}) => _lock.synchronized(() async {
28-
final settings = _settings;
29-
if (!settings.enabled) return null;
30-
try {
31-
await fileSystem.initialize();
32-
if (pathKey != null) {
33-
final byPath = await _getFileOrNull(pathKey);
34-
if (byPath != null) return _applySettings(byPath, settings);
35-
}
36-
if (allowContentHash && contentHash != null) {
37-
final byContent = await _getFileOrNull(
38-
documentStateContentKey(contentHash),
39-
);
40-
if (byContent != null) return _applySettings(byContent, settings);
41-
}
42-
} on NetworkException catch (e, stackTrace) {
43-
// Document state is optional. A cached remote document must still open
44-
// when its separate state record is unavailable offline.
45-
talker.warning('Failed to load document state', e, stackTrace);
27+
}) {
28+
final canLoadByPath = pathKey != null;
29+
final canLoadByContent = allowContentHash && contentHash != null;
30+
31+
if (!canLoadByPath && !canLoadByContent) {
32+
return Future.value(null);
4633
}
47-
return null;
48-
});
34+
return _lock.synchronized(() async {
35+
final settings = _settings;
36+
if (!settings.enabled) return null;
37+
try {
38+
await fileSystem.initialize();
39+
if (pathKey != null) {
40+
final byPath = await _getFileOrNull(pathKey);
41+
if (byPath != null) return _applySettings(byPath, settings);
42+
}
43+
if (allowContentHash && contentHash != null) {
44+
final byContent = await _getFileOrNull(
45+
documentStateContentKey(contentHash),
46+
);
47+
if (byContent != null) return _applySettings(byContent, settings);
48+
}
49+
} on NetworkException catch (e, stackTrace) {
50+
// Document state is optional. A cached remote document must still open
51+
// when its separate state record is unavailable offline.
52+
talker.warning('Failed to load document state', e, stackTrace);
53+
}
54+
return null;
55+
});
56+
}
4957

5058
Future<void> save(
5159
PersistedDocumentState state, {
@@ -54,17 +62,33 @@ class DocumentStateRepository {
5462
String? previousContentKey,
5563
String? previousPathKey,
5664
required bool persistentChanged,
57-
}) => _lock.synchronized(() async {
58-
final settings = _settings;
59-
if (!settings.enabled) return;
60-
await fileSystem.initialize();
61-
await _updateFile(previousPathKey, pathKey, state, persistentChanged);
62-
await _updateFile(previousContentKey, contentKey, state, persistentChanged);
63-
_scheduleCleanupAfterSave(
64-
contentHash: state.contentHash,
65-
pathKey: state.pathKey,
66-
);
67-
});
65+
}) {
66+
final hasAnyKey =
67+
contentKey != null ||
68+
pathKey != null ||
69+
previousContentKey != null ||
70+
previousPathKey != null;
71+
72+
if (!hasAnyKey) {
73+
return Future.value();
74+
}
75+
return _lock.synchronized(() async {
76+
final settings = _settings;
77+
if (!settings.enabled) return;
78+
await fileSystem.initialize();
79+
await _updateFile(previousPathKey, pathKey, state, persistentChanged);
80+
await _updateFile(
81+
previousContentKey,
82+
contentKey,
83+
state,
84+
persistentChanged,
85+
);
86+
_scheduleCleanupAfterSave(
87+
contentHash: state.contentHash,
88+
pathKey: state.pathKey,
89+
);
90+
});
91+
}
6892

6993
Future<int> cleanup({String? contentHash, String? pathKey, DateTime? now}) =>
7094
_lock.synchronized(() async {

app/lib/views/main.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,11 @@ class _ProjectPageState extends State<ProjectPage> {
343343
path: widget.location?.path ?? '',
344344
remote: remote?.identifier ?? '',
345345
);
346-
final pathKey = documentStatePathKeyOrNull(location);
347-
final contentHash = loadedDocumentBytes == null
346+
final persistDocumentState = embedding == null;
347+
final pathKey = persistDocumentState
348+
? documentStatePathKeyOrNull(location)
349+
: null;
350+
final contentHash = !persistDocumentState || loadedDocumentBytes == null
348351
? null
349352
: documentStateContentHash(loadedDocumentBytes);
350353
final documentStateRepository = DocumentStateRepository(

app/test/views/project_page_lifecycle_test.dart

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import 'package:butterfly/api/open.dart';
33
import 'package:butterfly/bloc/document_bloc.dart';
44
import 'package:butterfly/cubits/editor_controller.dart';
55
import 'package:butterfly/cubits/settings.dart';
6+
import 'package:butterfly/embed/embedding.dart';
67
import 'package:butterfly/models/defaults.dart';
8+
import 'package:butterfly/models/persisted_document_state.dart';
79
import 'package:butterfly/services/font.dart';
810
import 'package:butterfly/src/generated/i18n/app_localizations.dart';
911
import 'package:butterfly/views/main.dart';
@@ -95,11 +97,13 @@ void main() {
9597
);
9698
}
9799

98-
Widget buildApp() {
99-
final document = DocumentDefaults.createDocument(
100-
name: 'Lifecycle test',
101-
page: const DocumentPage(backgrounds: []),
102-
);
100+
Widget buildApp({NoteData? embedDocument}) {
101+
final document =
102+
embedDocument ??
103+
DocumentDefaults.createDocument(
104+
name: 'Lifecycle test',
105+
page: const DocumentPage(backgrounds: []),
106+
);
103107
fileSystem.buildTemplateSystem().updateFile('default', document);
104108
router = GoRouter(
105109
initialLocation: '/',
@@ -136,6 +140,13 @@ void main() {
136140
path: 'import',
137141
builder: (context, state) => ProjectPage(data: document),
138142
),
143+
GoRoute(
144+
path: 'embed',
145+
builder: (context, state) => ProjectPage(
146+
data: document.toFile(),
147+
embedding: Embedding(internal: true),
148+
),
149+
),
139150
],
140151
),
141152
],
@@ -222,13 +233,59 @@ void main() {
222233
'imported document close',
223234
);
224235
});
236+
237+
testWidgets('embed does not load or save persistent document state', (
238+
tester,
239+
) async {
240+
final document = DocumentDefaults.createDocument(
241+
name: 'Embed lifecycle test',
242+
page: const DocumentPage(backgrounds: []),
243+
);
244+
final contentKey = documentStateContentKey(
245+
documentStateContentHash(document.exportAsBytes()),
246+
);
247+
final documentStateSystem = fileSystem.buildDocumentStateSystem();
248+
await documentStateSystem.initialize();
249+
await documentStateSystem.createFile(
250+
contentKey,
251+
const PersistedDocumentState(
252+
camera: PersistedCameraState(positionX: 100, positionY: 200, zoom: 3),
253+
),
254+
);
255+
await tester.pumpWidget(buildApp(embedDocument: document));
256+
257+
router.go('/embed');
258+
await pumpUntil(
259+
tester,
260+
() => observer.lastDocumentBloc?.state is DocumentLoadSuccess,
261+
'embedded document open',
262+
);
263+
264+
final editorController = observer.lastDocumentBloc!.editorController;
265+
expect(editorController.transformCubit.state.position, Offset.zero);
266+
expect(editorController.transformCubit.state.size, 1);
267+
editorController.transformCubit.teleport(const Offset(10, 20), 2);
268+
269+
router.go('/');
270+
await pumpUntil(
271+
tester,
272+
() => observer.documentBlocCloses == 1,
273+
'embedded document close',
274+
);
275+
276+
final stored = await documentStateSystem.getFile(contentKey);
277+
expect(stored?.camera.positionX, 100);
278+
expect(stored?.camera.positionY, 200);
279+
expect(stored?.camera.zoom, 3);
280+
});
225281
}
226282

227283
class _LifecycleObserver extends BlocObserver {
228284
int documentBlocCreates = 0;
229285
int documentBlocCloses = 0;
230286
int saveCubitCreates = 0;
231287
int saveCubitCloses = 0;
288+
DocumentBloc? lastDocumentBloc;
232289
DocumentSaveCubit? lastSaveCubit;
233290
final events = <String>[];
234291

@@ -237,6 +294,7 @@ class _LifecycleObserver extends BlocObserver {
237294
super.onCreate(bloc);
238295
if (bloc is DocumentBloc) {
239296
documentBlocCreates++;
297+
lastDocumentBloc = bloc;
240298
} else if (bloc is DocumentSaveCubit) {
241299
saveCubitCreates++;
242300
lastSaveCubit = bloc;

metadata/en-US/changelogs/188.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Fix crash with android saf on folders with many files
1010
* Fix blur resetting on color change
1111
* Fix polygon collision aabb tests if closed ([#1162](https://github.com/LinwoodDev/Butterfly/pull/1162))
12+
* Fix embed/web loading errors ([#1167](https://github.com/LinwoodDev/Butterfly/issues/1167))
1213
* Upgrade to agb 9
1314

1415
Read more here: https://linwood.dev/butterfly/2.6.0-beta.2

0 commit comments

Comments
 (0)