diff --git a/tab_batch_ng.py b/tab_batch_ng.py index d960449..d29ef70 100644 --- a/tab_batch_ng.py +++ b/tab_batch_ng.py @@ -277,9 +277,10 @@ def render_batch_processor(state: AppState): new_item.pop(k, None) batch_list.append(new_item) data[KEY_BATCH_DATA] = batch_list - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) render_sequence_list.refresh() with ui.row().classes('q-mt-sm'): @@ -315,9 +316,10 @@ def render_batch_processor(state: AppState): async def sort_by_number(): batch_list.sort(key=lambda s: int(s.get(KEY_SEQUENCE_NUMBER, 0))) data[KEY_BATCH_DATA] = batch_list - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) ui.notify('Sorted by sequence number!', type='positive') render_sequence_list.refresh() @@ -369,11 +371,12 @@ def render_batch_processor(state: AppState): return data[KEY_HISTORY_TREE] = htree.to_dict() t1 = time.perf_counter() - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) logger.info("save_and_snap save_json %.3fs", time.perf_counter() - t1) if state.db_enabled and state.current_project and state.db: t1 = time.perf_counter() - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) logger.info("save_and_snap sync_to_db %.3fs", time.perf_counter() - t1) state.restored_indicator = None commit_input.set_value('') @@ -392,9 +395,10 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state, refresh_list): async def commit(message=None): data[KEY_BATCH_DATA] = batch_list - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) if message: ui.notify(message, type='positive') refresh_list.refresh() @@ -689,9 +693,10 @@ def _render_vace_settings(i, seq, batch_list, data, file_path, state, refresh_li batch_list[j].get('frame_to_skip', FRAME_TO_SKIP_DEFAULT), FRAME_TO_SKIP_DEFAULT) + delta shifted += 1 data[KEY_BATCH_DATA] = batch_list - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) ui.notify(f'Shifted {shifted} sequences by {delta:+d}', type='positive') refresh_list.refresh() @@ -840,9 +845,10 @@ def _render_mass_update(batch_list, data, file_path, state: AppState, refresh_li ui.notify(f'Mass update failed: {e}', type='negative') return data[KEY_HISTORY_TREE] = htree.to_dict() - await asyncio.to_thread(save_json, file_path, data) + save_snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, save_snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, save_snapshot) ui.notify(f'Updated {len(targets)} sequences', type='positive') if refresh_list: refresh_list.refresh() diff --git a/tab_timeline_ng.py b/tab_timeline_ng.py index aebdf2b..5574bf6 100644 --- a/tab_timeline_ng.py +++ b/tab_timeline_ng.py @@ -160,9 +160,10 @@ def _render_batch_delete(htree, data, file_path, state, refresh_fn): async def do_batch_delete(): current_valid = state.timeline_selected_nodes & set(htree.nodes.keys()) _delete_nodes(htree, data, file_path, current_valid) - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) state.timeline_selected_nodes = set() ui.notify( f'Deleted {len(current_valid)} node{"s" if len(current_valid) != 1 else ""}!', @@ -327,9 +328,10 @@ def _render_node_manager(all_nodes, htree, data, file_path, restore_fn, refresh_ if sel_id in htree.nodes and rename_input.value: htree.nodes[sel_id]['note'] = rename_input.value data[KEY_HISTORY_TREE] = htree.to_dict() - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state and state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) ui.notify('Label updated', type='positive') refresh_fn() @@ -343,9 +345,10 @@ def _render_node_manager(all_nodes, htree, data, file_path, restore_fn, refresh_ async def delete_selected(): if sel_id in htree.nodes: _delete_nodes(htree, data, file_path, {sel_id}) - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state and state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) # Reset selection if branch was removed if selected['branch'] not in htree.branches: selected['branch'] = next(iter(htree.branches), None) @@ -576,9 +579,10 @@ async def _restore_node(data, node, htree, file_path, state: AppState): data['history_tree_backup'] = preserved_backup htree.head_id = node['id'] data[KEY_HISTORY_TREE] = htree.to_dict() - await asyncio.to_thread(save_json, file_path, data) + snapshot = json.loads(json.dumps(data)) + await asyncio.to_thread(save_json, file_path, snapshot) if state.db_enabled and state.current_project and state.db: - await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, data) + await asyncio.to_thread(sync_to_db, state.db, state.current_project, file_path, snapshot) label = f"{node.get('note', 'Step')} ({node['id'][:4]})" state.restored_indicator = label logger.info("_restore_node END (%.3fs)", time.perf_counter() - t0)