Skip to content

Commit 10a4700

Browse files
committed
Reduxify Mode Service and start working on RemoveFile removal
1 parent 54e480d commit 10a4700

File tree

22 files changed

+255
-198
lines changed

22 files changed

+255
-198
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src/components/BranchList.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
conflictResolutionConfirmationModal?.show();
8787
return;
8888
}
89-
modeService!.enterEditMode(args.commitId, stackId);
89+
modeService!.enterEditMode({ commitId: args.commitId, stackId, projectId });
9090
}
9191
9292
const selectedCommit = $derived(

apps/desktop/src/components/CommitView.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
137137
async function editPatch() {
138138
if (!canEdit()) return;
139-
await modeService!.enterEditMode(commitKey.commitId, stackId);
139+
await modeService!.enterEditMode({ commitId: commitKey.commitId, stackId, projectId });
140140
}
141141
142142
function cancelEdit() {

apps/desktop/src/components/EditMode.svelte

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import Avatar from '@gitbutler/ui/avatar/Avatar.svelte';
3030
import FileListItem from '@gitbutler/ui/file/FileListItem.svelte';
3131
import { SvelteSet } from 'svelte/reactivity';
32+
import type { TreeChange } from '$lib/hunks/change';
3233
import type { FileStatus } from '@gitbutler/ui/file/types';
3334
import type { Writable } from 'svelte/store';
3435
@@ -55,7 +56,7 @@
5556
let modeServiceAborting = $state<'inert' | 'loading' | 'completed'>('inert');
5657
let modeServiceSaving = $state<'inert' | 'loading' | 'completed'>('inert');
5758
58-
let initialFiles = $state<[RemoteFile, ConflictEntryPresence | undefined][]>([]);
59+
let initialFiles = $derived(modeService.initialEditModeState({ projectId: project.id }));
5960
let commit = $state<Commit>();
6061
6162
async function getCommitData() {
@@ -79,15 +80,8 @@
7980
let contextMenu = $state<ReturnType<typeof FileContextMenu> | undefined>(undefined);
8081
let confirmSaveModal = $state<ReturnType<typeof Modal> | undefined>(undefined);
8182
82-
$effect(() => {
83-
modeService.getInitialIndexState().then((files) => {
84-
initialFiles = files;
85-
});
86-
});
87-
8883
interface FileEntry {
8984
conflicted: boolean;
90-
name: string;
9185
path: string;
9286
status?: FileStatus;
9387
conflictHint?: string;
@@ -96,26 +90,28 @@
9690
}
9791
9892
const initialFileMap = $derived(
99-
new Map<string, RemoteFile>(initialFiles.map(([file]) => [file.path, file]))
93+
new Map<string, TreeChange>(
94+
initialFiles.current?.data?.map(([file]) => [file.path, file]) || []
95+
)
10096
);
10197
10298
const uncommitedFileMap = $derived(
10399
new Map<string, RemoteFile>($uncommitedFiles.map(([file]) => [file.path, file]))
104100
);
105101
106102
const files = $derived.by(() => {
103+
if (!initialFiles.current.data) return [];
104+
107105
const outputMap = new Map<string, FileEntry>();
108106
109107
// Create output
110108
{
111-
initialFiles.forEach(([initialFile, conflictEntryPresence]) => {
112-
const conflictState =
113-
conflictEntryPresence && getConflictState(initialFile, conflictEntryPresence);
109+
initialFiles.current.data.forEach(([initialFile, conflictEntryPresence]) => {
110+
const conflictState = conflictEntryPresence && 'unknown'; //getConflictState(initialFile, conflictEntryPresence);
114111
115112
const uncommitedFileChange = uncommitedFileMap.get(initialFile.path);
116113
117114
outputMap.set(initialFile.path, {
118-
name: initialFile.filename,
119115
path: initialFile.path,
120116
conflicted: !!conflictEntryPresence,
121117
conflictHint: conflictEntryPresence
@@ -131,9 +127,10 @@
131127
const existingFile = initialFileMap.get(uncommitedFile.path);
132128
determineOutput: {
133129
if (existingFile) {
134-
const fileChanged = existingFile.hunks.some(
135-
(hunk) => !uncommitedFile.hunks.map((hunk) => hunk.diff).includes(hunk.diff)
136-
);
130+
const fileChanged = false;
131+
// const fileChanged = existingFile.hunks.some(
132+
// (hunk) => !uncommitedFile.hunks.map((hunk) => hunk.diff).includes(hunk.diff)
133+
// );
137134
138135
if (fileChanged) {
139136
// All initial entries should have been added to the map,
@@ -154,7 +151,6 @@
154151
}
155152
156153
outputMap.set(uncommitedFile.path, {
157-
name: uncommitedFile.filename,
158154
path: uncommitedFile.path,
159155
conflicted: false,
160156
status: computeFileStatus(uncommitedFile)
@@ -197,7 +193,7 @@
197193
modeServiceAborting = 'loading';
198194
199195
try {
200-
await modeService.abortEditAndReturnToWorkspace();
196+
await modeService.abortEditAndReturnToWorkspace({ projectId: project.id });
201197
modeServiceAborting = 'completed';
202198
} finally {
203199
modeServiceAborting = 'inert';
@@ -208,7 +204,7 @@
208204
modeServiceSaving = 'loading';
209205
210206
try {
211-
await modeService.saveEditAndReturnToWorkspace();
207+
await modeService.saveEditAndReturnToWorkspace({ projectId: project.id });
212208
modeServiceSaving = 'completed';
213209
} finally {
214210
modeServiceAborting = 'inert';
Lines changed: 105 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { invoke, listen } from '$lib/backend/ipc';
2-
import { RemoteFile } from '$lib/files/file';
3-
import { plainToInstance } from 'class-transformer';
4-
import { derived, writable } from 'svelte/store';
1+
import { hasTauriExtra } from '$lib/state/backendQuery';
2+
import { invalidatesList, providesList, ReduxTag } from '$lib/state/tags';
53
import type { ConflictEntryPresence } from '$lib/conflictEntryPresence';
6-
import type { StackService } from '$lib/stacks/stackService.svelte';
4+
import type { TreeChange } from '$lib/hunks/change';
5+
import type { ClientState } from '$lib/state/clientState.svelte';
76

87
export interface EditModeMetadata {
98
commitOid: string;
@@ -33,79 +32,120 @@ interface HeadAndMode {
3332
}
3433

3534
export class ModeService {
36-
private headAndMode = writable<HeadAndMode>({}, (set) => {
37-
this.refresh();
35+
private api: ReturnType<typeof injectEndpoints>;
3836

39-
const unsubscribe = subscribeToHead(this.projectId, (headAndMode) => {
40-
set(headAndMode);
41-
});
42-
43-
return unsubscribe;
44-
});
45-
46-
readonly head = derived(this.headAndMode, ({ head }) => head);
47-
readonly mode = derived(this.headAndMode, ({ operatingMode }) => operatingMode);
48-
49-
constructor(
50-
private projectId: string,
51-
private readonly stackService: StackService
52-
) {}
53-
54-
private async refresh() {
55-
const head = await invoke<string>('git_head', { projectId: this.projectId });
56-
const operatingMode = await invoke<Mode>('operating_mode', { projectId: this.projectId });
57-
58-
this.headAndMode.set({ head, operatingMode });
37+
constructor(state: ClientState['backendApi']) {
38+
this.api = injectEndpoints(state);
5939
}
6040

61-
async enterEditMode(commitId: string, stackId: string) {
62-
this.stackService.enterEditMode({
63-
projectId: this.projectId,
64-
commitId,
65-
stackId
66-
});
67-
await this.awaitMode('Edit');
41+
get enterEditMode() {
42+
return this.api.endpoints.enterEditMode.mutate;
6843
}
6944

70-
async abortEditAndReturnToWorkspace() {
71-
await this.stackService.abortEditAndReturnToWorkspace({
72-
projectId: this.projectId
73-
});
74-
await this.awaitMode('OpenWorkspace');
45+
get abortEditAndReturnToWorkspace() {
46+
return this.api.endpoints.abortEditAndReturnToWorkspace.mutate;
7547
}
7648

77-
async saveEditAndReturnToWorkspace() {
78-
await this.stackService.saveEditAndReturnToWorkspace({
79-
projectId: this.projectId
80-
});
81-
await this.awaitMode('OpenWorkspace');
49+
get saveEditAndReturnToWorkspace() {
50+
return this.api.endpoints.saveEditAndReturnToWorkspace.mutate;
8251
}
8352

84-
async getInitialIndexState() {
85-
const rawOutput = await invoke<unknown[][]>('edit_initial_index_state', {
86-
projectId: this.projectId
87-
});
88-
89-
return rawOutput.map((entry) => {
90-
return [plainToInstance(RemoteFile, entry[0]), entry[1] as ConflictEntryPresence | undefined];
91-
}) as [RemoteFile, ConflictEntryPresence | undefined][];
53+
get initialEditModeState() {
54+
return this.api.endpoints.initialEditModeState.useQuery;
9255
}
9356

94-
async awaitMode(mode: Mode['type']): Promise<void> {
95-
return await new Promise((resolve) => {
96-
const unsubscribe = this.mode.subscribe((operatingMode) => {
97-
if (operatingMode && operatingMode?.type === mode) {
98-
resolve();
57+
get mode() {
58+
return this.api.endpoints.mode.useQuery;
59+
}
9960

100-
setTimeout(() => {
101-
unsubscribe();
102-
}, 0);
103-
}
104-
});
105-
});
61+
get head() {
62+
return this.api.endpoints.mode.useQuery;
10663
}
10764
}
10865

109-
function subscribeToHead(projectId: string, callback: (headAndMode: HeadAndMode) => void) {
110-
return listen<HeadAndMode>(`project://${projectId}/git/head`, (event) => callback(event.payload));
66+
function injectEndpoints(api: ClientState['backendApi']) {
67+
return api.injectEndpoints({
68+
endpoints: (build) => ({
69+
enterEditMode: build.mutation<void, { projectId: string; commitId: string; stackId: string }>(
70+
{
71+
extraOptions: { command: 'enter_edit_mode' },
72+
query: (args) => args,
73+
invalidatesTags: [
74+
invalidatesList(ReduxTag.InitalEditListing),
75+
invalidatesList(ReduxTag.HeadMetadata)
76+
]
77+
}
78+
),
79+
abortEditAndReturnToWorkspace: build.mutation<void, { projectId: string }>({
80+
extraOptions: { command: 'abort_edit_and_return_to_workspace' },
81+
query: (args) => args,
82+
invalidatesTags: [
83+
invalidatesList(ReduxTag.InitalEditListing),
84+
invalidatesList(ReduxTag.HeadMetadata)
85+
]
86+
}),
87+
saveEditAndReturnToWorkspace: build.mutation<void, { projectId: string }>({
88+
extraOptions: { command: 'save_edit_and_return_to_workspace' },
89+
query: (args) => args,
90+
invalidatesTags: [
91+
invalidatesList(ReduxTag.WorktreeChanges),
92+
invalidatesList(ReduxTag.StackDetails),
93+
invalidatesList(ReduxTag.InitalEditListing),
94+
invalidatesList(ReduxTag.HeadMetadata)
95+
]
96+
}),
97+
initialEditModeState: build.query<
98+
[TreeChange, ConflictEntryPresence | undefined][],
99+
{ projectId: string }
100+
>({
101+
extraOptions: { command: 'edit_initial_index_state' },
102+
query: (args) => args,
103+
providesTags: [providesList(ReduxTag.InitalEditListing)]
104+
}),
105+
mode: build.query<Mode, { projectId: string }>({
106+
extraOptions: { command: 'operating_mode' },
107+
query: (args) => args,
108+
providesTags: [providesList(ReduxTag.HeadMetadata)],
109+
async onCacheEntryAdded(arg, lifecycleApi) {
110+
if (!hasTauriExtra(lifecycleApi.extra)) {
111+
throw new Error('Redux dependency Tauri not found!');
112+
}
113+
await lifecycleApi.cacheDataLoaded;
114+
const unsubscribe = lifecycleApi.extra.tauri.listen<HeadAndMode>(
115+
`project://${arg.projectId}/head`,
116+
(event) => {
117+
lifecycleApi.updateCachedData(() => event.payload.operatingMode);
118+
lifecycleApi.dispatch(
119+
api.util.invalidateTags([invalidatesList(ReduxTag.HeadMetadata)])
120+
);
121+
}
122+
);
123+
await lifecycleApi.cacheEntryRemoved;
124+
unsubscribe();
125+
}
126+
}),
127+
head: build.query<string, { projectId: string }>({
128+
extraOptions: { command: 'git_head' },
129+
query: (args) => args,
130+
providesTags: [providesList(ReduxTag.HeadMetadata)],
131+
async onCacheEntryAdded(arg, lifecycleApi) {
132+
if (!hasTauriExtra(lifecycleApi.extra)) {
133+
throw new Error('Redux dependency Tauri not found!');
134+
}
135+
await lifecycleApi.cacheDataLoaded;
136+
const unsubscribe = lifecycleApi.extra.tauri.listen<HeadAndMode>(
137+
`project://${arg.projectId}/head`,
138+
(event) => {
139+
lifecycleApi.updateCachedData(() => event.payload.head);
140+
lifecycleApi.dispatch(
141+
api.util.invalidateTags([invalidatesList(ReduxTag.HeadMetadata)])
142+
);
143+
}
144+
);
145+
await lifecycleApi.cacheEntryRemoved;
146+
unsubscribe();
147+
}
148+
})
149+
})
150+
});
111151
}

apps/desktop/src/lib/stacks/stackService.svelte.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -805,18 +805,6 @@ export class StackService {
805805
);
806806
}
807807

808-
get enterEditMode() {
809-
return this.api.endpoints.enterEditMode.mutate;
810-
}
811-
812-
get abortEditAndReturnToWorkspace() {
813-
return this.api.endpoints.abortEditAndReturnToWorkspace.mutate;
814-
}
815-
816-
get saveEditAndReturnToWorkspace() {
817-
return this.api.endpoints.saveEditAndReturnToWorkspace.mutate;
818-
}
819-
820808
get splitBranch() {
821809
return this.api.endpoints.splitBranch.useMutation();
822810
}
@@ -1516,24 +1504,6 @@ function injectEndpoints(api: ClientState['backendApi']) {
15161504
transformResponse: (commits: Commit[]) =>
15171505
commitAdapter.addMany(commitAdapter.getInitialState(), commits)
15181506
}),
1519-
enterEditMode: build.mutation<void, { projectId: string; commitId: string; stackId: string }>(
1520-
{
1521-
extraOptions: { command: 'enter_edit_mode' },
1522-
query: (args) => args
1523-
}
1524-
),
1525-
abortEditAndReturnToWorkspace: build.mutation<void, { projectId: string }>({
1526-
extraOptions: { command: 'abort_edit_and_return_to_workspace' },
1527-
query: (args) => args
1528-
}),
1529-
saveEditAndReturnToWorkspace: build.mutation<void, { projectId: string }>({
1530-
extraOptions: { command: 'save_edit_and_return_to_workspace' },
1531-
query: (args) => args,
1532-
invalidatesTags: [
1533-
invalidatesList(ReduxTag.WorktreeChanges),
1534-
invalidatesList(ReduxTag.StackDetails)
1535-
]
1536-
}),
15371507
splitBranch: build.mutation<
15381508
{ replacedCommits: [string, string][] },
15391509
{

apps/desktop/src/lib/state/tags.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// TODO: Refactor this enum into an object conataining invalidation rules.
22
export enum ReduxTag {
3+
InitalEditListing = 'InitialEditListing',
4+
HeadMetadata = 'HeadMetadata',
35
Diff = 'Diff',
46
HunkAssignments = 'HunkAssignments',
57
Stacks = 'Stacks',

0 commit comments

Comments
 (0)