-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathdocumentation-hooks.tsx
162 lines (152 loc) · 4.23 KB
/
documentation-hooks.tsx
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
* (c) 2021-2022, Micro:bit Educational Foundation and contributors
*
* SPDX-License-Identifier: MIT
*/
import {
createContext,
ReactNode,
RefObject,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { apiDocs, ApiDocsContent } from "../language-server/apidocs";
import { useLanguageServerClient } from "../language-server/language-server-hooks";
import { useLogging } from "../logging/logging-hooks";
import { useSettings } from "../settings/settings";
import {
filterOutUndocumentedBuiltins,
pullModulesToTop,
} from "./api/apidocs-util";
import dragImage from "./drag-image.svg";
import { fetchIdeas } from "./ideas/content";
import { Idea } from "./ideas/model";
import { ApiReferenceMap, fetchMappingData } from "./mapping/content";
import { fetchReferenceToolkit } from "./reference/content";
import { Toolkit } from "./reference/model";
export type ContentState<T> =
| { status: "ok"; content: T; languageId: string }
| { status: "error" }
| { status: "loading" };
const useContent = <T,>(
fetchContent: (languageId: string) => Promise<T>
): ContentState<T> => {
const [state, setState] = useState<ContentState<T>>({
status: "loading",
});
const logging = useLogging();
const [{ languageId }] = useSettings();
useEffect(() => {
let ignore = false;
const load = async () => {
try {
const content = await fetchContent(languageId);
if (!ignore) {
setState({ status: "ok", content, languageId });
}
} catch (e) {
logging.error(e);
if (!ignore) {
setState({
status: "error",
});
}
}
};
load();
return () => {
ignore = true;
};
}, [setState, logging, languageId, fetchContent]);
return state;
};
const useApiDocumentation = (): ApiDocsContent | undefined => {
const client = useLanguageServerClient();
const [apidocs, setApiDocs] = useState<ApiDocsContent | undefined>();
useEffect(() => {
let ignore = false;
const load = async () => {
if (client) {
const docs = await apiDocs(client);
pullModulesToTop(docs);
filterOutUndocumentedBuiltins(docs);
if (!ignore) {
setApiDocs(docs);
}
}
};
load();
return () => {
ignore = true;
};
}, [client]);
return apidocs;
};
export interface DocumentationContextValue {
api: ApiDocsContent | undefined;
ideas: ContentState<Idea[]>;
reference: ContentState<Toolkit>;
apiReferenceMap: ContentState<ApiReferenceMap>;
}
const DocumentationContext = createContext<
DocumentationContextValue | undefined
>(undefined);
/**
* Aggregated documentation.
*/
export const useDocumentation = (): DocumentationContextValue => {
const value = useContext(DocumentationContext);
if (!value) {
throw new Error("Missing provider!");
}
return value;
};
const DocumentationProvider = ({ children }: { children: ReactNode }) => {
const api = useApiDocumentation();
const ideas = useContent(fetchIdeas);
const reference = useContent(fetchReferenceToolkit);
const apiReferenceMap = useContent(fetchMappingData);
const value: DocumentationContextValue = useMemo(() => {
return { reference, api, ideas, apiReferenceMap };
}, [reference, api, ideas, apiReferenceMap]);
return (
<DocumentationContext.Provider value={value}>
{children}
</DocumentationContext.Provider>
);
};
export default DocumentationProvider;
let dragImageRefCount = 0;
export const useCodeDragImage = (): RefObject<HTMLImageElement | undefined> => {
const ref = useRef<HTMLImageElement>();
useEffect(() => {
const id = "code-drag-image";
let img = document.getElementById(id) as HTMLImageElement | null;
if (!img) {
img = new Image();
img.id = id;
img.alt = "";
img.src = dragImage;
img.style.position = "absolute";
img.style.top = "0";
img.style.zIndex = "-1";
// Seems to need to be in the DOM for Safari.
document.body.appendChild(img);
}
ref.current = img;
dragImageRefCount++;
return () => {
if (!img) {
throw new Error();
}
dragImageRefCount--;
if (dragImageRefCount === 0) {
img.remove();
}
};
}, []);
return ref;
};