-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseFiles.ts
More file actions
91 lines (74 loc) · 3.02 KB
/
useFiles.ts
File metadata and controls
91 lines (74 loc) · 3.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import { useQuery } from '@tanstack/react-query';
import { useServerFn } from '@tanstack/react-start';
import { useCallback, useEffect, useState } from 'react';
import { BehaviorSubject } from 'rxjs';
import type { FileItem } from '../../utils/files/files';
import { getConfigFiles } from '../../utils/files/files.functions';
type TrackedFile = FileItem & {
hasChanges: boolean;
upstreamContent: string;
};
type State = Record<string, TrackedFile>;
// Shared globally
const FILES_SUBJECT = new BehaviorSubject<State>({});
/**
* Hook to fetch the current file content from the backend.
*/
export function useFiles() {
const filesFunction = useServerFn(getConfigFiles);
return useQuery({
queryFn: () => filesFunction(),
queryKey: ['files']
});
}
/**
* Smart hook to track local changes to files in comparison to the upstream content from the server. It provides a way to update the local state and determine if there are unsaved changes.
*/
export function useTrackedFiles() {
const remoteState = useFiles();
const [outputState, setOutputState] = useState<State>(FILES_SUBJECT.getValue());
const [subscription] = useState(() => FILES_SUBJECT.subscribe((state) => setOutputState(state)));
useEffect(() => () => subscription.unsubscribe(), [subscription]);
useEffect(() => {
// Whenever the upstream data changes (e.g. after save triggers refetch), merge server
// state into tracked state. Preserve existing.content only when there are unsaved
// local changes so refetch-after-save does not clobber in-flight keystrokes; when
// there are no local changes we take upstream content so Refresh/refetch updates the
// view. The editor is disabled during save/refetch (see BaseEditorWidget).
if (remoteState.data) {
const currentState = FILES_SUBJECT.getValue();
const nextState: State = { ...currentState };
for (const upstreamEntry of remoteState.data.files) {
const existing = currentState[upstreamEntry.filename];
const hasLocalChanges = existing && existing.content !== existing.upstreamContent;
nextState[upstreamEntry.filename] = {
...upstreamEntry,
content: hasLocalChanges ? existing.content : upstreamEntry.content,
filename: upstreamEntry.filename,
hasChanges: existing ? existing.content !== upstreamEntry.content : false,
upstreamContent: upstreamEntry.content
};
}
FILES_SUBJECT.next(nextState);
}
}, [remoteState.data]);
const updateLocalState = useCallback((filename: string, content: string) => {
const currentState = FILES_SUBJECT.getValue();
const existing = currentState[filename];
const nextState: State = {
...currentState,
[filename]: {
...existing,
content,
hasChanges: existing ? content !== existing.upstreamContent : false,
upstreamContent: existing?.upstreamContent ?? ''
}
};
FILES_SUBJECT.next(nextState);
}, []);
return {
state: outputState,
updateLocalState,
upstream: remoteState
};
}