tsondb 0.7.7 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/src/node/server/api/git.js +156 -24
  2. package/dist/src/node/server/index.js +4 -0
  3. package/dist/src/node/utils/instanceOperations.d.ts +1 -1
  4. package/dist/src/node/utils/instanceOperations.js +2 -2
  5. package/dist/src/shared/api.d.ts +17 -1
  6. package/dist/src/shared/utils/git.d.ts +4 -0
  7. package/dist/src/shared/utils/git.js +6 -1
  8. package/dist/src/shared/utils/markdown.d.ts +36 -17
  9. package/dist/src/shared/utils/markdown.js +79 -23
  10. package/dist/src/shared/utils/object.d.ts +1 -0
  11. package/dist/src/shared/utils/object.js +1 -0
  12. package/dist/src/web/api/git.d.ts +5 -1
  13. package/dist/src/web/api/git.js +6 -2
  14. package/dist/src/web/components/ContextProviderWrapper.d.ts +7 -0
  15. package/dist/src/web/components/ContextProviderWrapper.js +5 -0
  16. package/dist/src/web/components/InstanceRouteSkeleton.d.ts +1 -0
  17. package/dist/src/web/components/InstanceRouteSkeleton.js +8 -4
  18. package/dist/src/web/components/LoadingOverlay.d.ts +1 -0
  19. package/dist/src/web/components/LoadingOverlay.js +3 -0
  20. package/dist/src/web/components/ModalDialog.js +4 -3
  21. package/dist/src/web/components/git/Git.js +47 -0
  22. package/dist/src/web/components/git/GitBranchManager.d.ts +7 -0
  23. package/dist/src/web/components/git/GitBranchManager.js +17 -0
  24. package/dist/src/web/components/git/GitFileList.d.ts +17 -0
  25. package/dist/src/web/components/git/GitFileList.js +11 -0
  26. package/dist/src/web/components/git/GitFileManager.d.ts +8 -0
  27. package/dist/src/web/components/git/GitFileManager.js +34 -0
  28. package/dist/src/web/components/git/GitStatusIndicator.d.ts +7 -0
  29. package/dist/src/web/components/git/GitStatusIndicator.js +6 -0
  30. package/dist/src/web/components/typeInputs/StringTypeInput.js +1 -1
  31. package/dist/src/web/context/entities.d.ts +6 -5
  32. package/dist/src/web/context/git.d.ts +2 -1
  33. package/dist/src/web/context/gitClient.d.ts +2 -0
  34. package/dist/src/web/context/gitClient.js +2 -0
  35. package/dist/src/web/hooks/useGitClient.d.ts +37 -0
  36. package/dist/src/web/hooks/useGitClient.js +313 -0
  37. package/dist/src/web/index.js +6 -2
  38. package/dist/src/web/routes/CreateInstance.js +2 -1
  39. package/dist/src/web/routes/Entity.js +10 -4
  40. package/dist/src/web/routes/Instance.js +2 -1
  41. package/dist/src/web/signals/loading.d.ts +2 -0
  42. package/dist/src/web/signals/loading.js +11 -0
  43. package/dist/src/web/utils/BlockMarkdown.js +1 -1
  44. package/dist/src/web/utils/debug.d.ts +1 -0
  45. package/dist/src/web/utils/debug.js +4 -0
  46. package/package.json +3 -3
  47. package/public/css/styles.css +132 -70
  48. package/dist/src/web/components/Git.js +0 -164
  49. /package/dist/src/web/components/{Git.d.ts → git/Git.d.ts} +0 -0
@@ -1,3 +1,5 @@
1
+ import { omitUndefinedKeys } from "./object.js";
2
+ import { assertExhaustive } from "./typeSafety.js";
1
3
  const codeRule = {
2
4
  pattern: /`(.*?)`/,
3
5
  map: result => ({
@@ -122,13 +124,12 @@ const parsedAttributesLength = (rawAttributes) => rawAttributes.reduce((sum, att
122
124
  : attr.name.length + attr.separator.length + attr.rawValue.length), 0);
123
125
  const attributedRule = {
124
126
  pattern: /(?<!\\)\^\[(.*?[^\\])\]\(((?:\w+: *(?:true|false|\d+(?:\.\d+)?|"(.*?)(?<!\\)"|'(.*?)(?<!\\)'))(?:, *\w+: *(?:true|false|\d+(?:\.\d+)?|"(.*?)(?<!\\)"|'(.*?)(?<!\\)'))*)\)/,
125
- map: (result, parseInside) => ({
127
+ map: ([_res, content = "", attributesText = ""], parseInside) => ({
126
128
  kind: "attributed",
127
- attributes: mapAttributesToObject(parseAttributes(result[2] ?? "")),
128
- content: parseInside(result[1] ?? ""),
129
+ attributes: mapAttributesToObject(parseAttributes(attributesText)),
130
+ content: parseInside(content),
129
131
  }),
130
- mapHighlighting: (result, parseInside) => {
131
- const attributesText = result[2] ?? "";
132
+ mapHighlighting: ([_res, content = "", attributesText = ""], parseInside) => {
132
133
  const attributes = parseAttributes(attributesText);
133
134
  const length = parsedAttributesLength(attributes);
134
135
  const unparsedText = attributesText.length > length
@@ -139,7 +140,7 @@ const attributedRule = {
139
140
  attributes: mapAttributesToObject(attributes),
140
141
  content: [
141
142
  textNode("^["),
142
- ...parseInside(result[1] ?? ""),
143
+ ...parseInside(content),
143
144
  textNode("]("),
144
145
  ...mapAttributesToNodes(attributes),
145
146
  ...unparsedText,
@@ -154,7 +155,7 @@ const textNode = (content) => ({
154
155
  });
155
156
  const parseEscapedCharacters = (text) => text.replace(/\\([*_`[\]()\\])/g, "$1");
156
157
  const textRule = {
157
- pattern: /.+/,
158
+ pattern: /.+/s,
158
159
  map: result => ({
159
160
  kind: "text",
160
161
  content: parseEscapedCharacters(result[0]),
@@ -225,13 +226,13 @@ const listRule = {
225
226
  };
226
227
  const paragraphRule = {
227
228
  pattern: /^((?:[^\n]+?)(?:\n[^\n]+?)*)(\n{2,}|\s*$)/,
228
- map: result => ({
229
+ map: ([_res, content = "", _trailingWhitespace]) => ({
229
230
  kind: "paragraph",
230
- content: parseInlineMarkdown(result[1] ?? "", false),
231
+ content: parseInlineMarkdown(content, false),
231
232
  }),
232
- mapHighlighting: result => [
233
- ...parseInlineMarkdown(result[1] ?? "", true),
234
- ...nodesForTrailingWhitespace(result[2]),
233
+ mapHighlighting: ([_res, content = "", trailingWhitespace]) => [
234
+ ...parseInlineMarkdown(content, true),
235
+ ...nodesForTrailingWhitespace(trailingWhitespace),
235
236
  ],
236
237
  };
237
238
  const headingRule = {
@@ -253,26 +254,35 @@ const tableMarker = (text) => ({
253
254
  content: text,
254
255
  });
255
256
  const tableRule = {
256
- pattern: /^(\| *)?(.+?(?: *(?<!\\)\| *.+?)+)( *\|)?\n((?:\| *)?(?:-{3,}|:-{2,}|-{2,}:|:-+:)(?: *\| *(?:-{3,}|:-{2,}|-{2,}:|:-+:))*(?: *\|)?)((?:\n\|? *.+?(?: *(?<!\\)\| *.+?)* *(?<!\\)\|?)+)(\n{2,}|$)/,
257
- map: result => ({
257
+ pattern: /^(?:(\|#)(.+?)(#\|)\n)?(\|)?(.+?(?:(?<!\\)\|.+?)+)((?<!\\)\|)?\n((?:\| *)?(?:-{3,}|:-{2,}|-{2,}:|:-+:)(?: *\| *(?:-{3,}|:-{2,}|-{2,}:|:-+:))*(?: *\|)?)((?:\n\|?.+?(?:(?<!\\)\|.+?)*(?<!\\)\|?)+)(\n{2,}|$)/,
258
+ map: ([_res, _captionMarkerStart, caption, _captionMarkerEnd, _headerMarkerStart, headers, _headerMarkerEnd, _bodySeparators, body, _trailingWhitespace,]) => omitUndefinedKeys({
258
259
  kind: "table",
259
- header: result[2]?.split("|").map(th => parseInlineMarkdown(th.trim(), false)) ?? [],
260
- rows: result[5]
260
+ caption: caption !== undefined ? parseInlineMarkdown(caption.trim(), false) : undefined,
261
+ header: headers?.split("|").map(th => parseInlineMarkdown(th.trim(), false)) ?? [],
262
+ rows: body
261
263
  ?.split("\n")
262
264
  .slice(1)
263
265
  .map(tr => removeSurroundingPipes(tr)
264
266
  .split("|")
265
267
  .map(tc => parseInlineMarkdown(tc.trim(), false))) ?? [],
266
268
  }),
267
- mapHighlighting: result => [
268
- tableMarker(result[1] ?? ""),
269
- ...(result[2]
269
+ mapHighlighting: ([_res, captionMarkerStart, caption, captionMarkerEnd, headerMarkerStart, headers, headerMarkerEnd, bodySeparators, body, trailingWhitespace,]) => [
270
+ ...(caption !== undefined
271
+ ? [
272
+ tableMarker(captionMarkerStart ?? ""),
273
+ ...parseInlineMarkdown(caption, true),
274
+ tableMarker(captionMarkerEnd ?? ""),
275
+ textNode("\n"),
276
+ ]
277
+ : []),
278
+ tableMarker(headerMarkerStart ?? ""),
279
+ ...(headers
270
280
  ?.split("|")
271
281
  .flatMap((th, i) => i === 0
272
282
  ? parseInlineMarkdown(th, true)
273
283
  : [tableMarker("|"), ...parseInlineMarkdown(th, true)]) ?? []),
274
- tableMarker((result[3] ?? "") + "\n" + (result[4] ?? "")),
275
- ...(result[5]
284
+ tableMarker((headerMarkerEnd ?? "") + "\n" + (bodySeparators ?? "")),
285
+ ...(body
276
286
  ?.split("\n")
277
287
  .slice(1)
278
288
  .flatMap((tr) => [
@@ -283,7 +293,7 @@ const tableRule = {
283
293
  ? parseInlineMarkdown(tc, true)
284
294
  : [tableMarker("|"), ...parseInlineMarkdown(tc, true)]),
285
295
  ]) ?? []),
286
- ...nodesForTrailingWhitespace(result[6]),
296
+ ...nodesForTrailingWhitespace(trailingWhitespace),
287
297
  ],
288
298
  };
289
299
  const blockRules = [headingRule, tableRule, listRule, paragraphRule];
@@ -310,4 +320,50 @@ const parseForBlockRules = (rules, text, ruleParser, remainingRules = rules) =>
310
320
  }
311
321
  };
312
322
  export const parseBlockMarkdown = (text) => parseForBlockRules(blockRules, text, parseActiveBlockRule);
313
- export const parseBlockMarkdownForSyntaxHighlighting = (text) => parseForBlockRules(blockRules, text, parseActiveBlockSyntaxRule);
323
+ export const parseBlockMarkdownForSyntaxHighlighting = (text) => reduceSyntaxNodes(parseForBlockRules(blockRules, text, parseActiveBlockSyntaxRule));
324
+ export const reduceSyntaxNodes = (nodes) => nodes.reduce((reducedNodes, node, index) => {
325
+ const lastNode = index > 0 ? reducedNodes[reducedNodes.length - 1] : undefined;
326
+ const newLastNode = lastNode ? mergeSyntaxNodes(lastNode, node) : null;
327
+ if (newLastNode) {
328
+ reducedNodes[reducedNodes.length - 1] = reduceSyntaxNode(newLastNode);
329
+ }
330
+ else {
331
+ reducedNodes.push(reduceSyntaxNode(node));
332
+ }
333
+ return reducedNodes;
334
+ }, []);
335
+ const reduceSyntaxNode = (node) => {
336
+ switch (node.kind) {
337
+ case "bold":
338
+ case "italic":
339
+ return { ...node, content: reduceSyntaxNodes(node.content) };
340
+ case "code":
341
+ case "link":
342
+ case "attributed":
343
+ case "text":
344
+ case "listitemmarker":
345
+ case "tablemarker":
346
+ case "headingmarker":
347
+ return node;
348
+ default:
349
+ return assertExhaustive(node);
350
+ }
351
+ };
352
+ const syntaxNodeMergeRules = {
353
+ bold: (a, b) => ({ ...a, content: [...a.content, ...b.content] }),
354
+ italic: (a, b) => ({ ...a, content: [...a.content, ...b.content] }),
355
+ code: (a, b) => ({ ...a, content: a.content + b.content }),
356
+ text: (a, b) => ({ ...a, content: a.content + b.content }),
357
+ listitemmarker: (a, b) => ({ ...a, content: a.content + b.content }),
358
+ tablemarker: (a, b) => ({ ...a, content: a.content + b.content }),
359
+ headingmarker: (a, b) => ({ ...a, content: a.content + b.content }),
360
+ link: null,
361
+ attributed: null,
362
+ };
363
+ const mergeSyntaxNodes = (lastNode, node) => {
364
+ if (lastNode.kind !== node.kind) {
365
+ return null;
366
+ }
367
+ const mergeFn = syntaxNodeMergeRules[lastNode.kind];
368
+ return mergeFn?.(lastNode, node) ?? null;
369
+ };
@@ -6,3 +6,4 @@ export type Leaves<T> = T extends object ? {
6
6
  }[keyof T] : never;
7
7
  export declare const onlyKeys: <T extends object, K extends keyof T>(obj: T, ...keys: K[]) => Pick<T, K>;
8
8
  export declare const hasKey: <T extends object, K extends PropertyKey>(obj: T, key: K) => obj is T & { [k in K]: unknown; };
9
+ export declare const omitUndefinedKeys: <T extends object>(obj: T) => T;
@@ -10,3 +10,4 @@ export const mergeObjects = (obj1, obj2, solveConflict) => Object.entries(obj2).
10
10
  }), obj1);
11
11
  export const onlyKeys = (obj, ...keys) => Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)));
12
12
  export const hasKey = (obj, key) => Object.hasOwn(obj, key);
13
+ export const omitUndefinedKeys = (obj) => Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined));
@@ -1,14 +1,18 @@
1
- import type { GetAllGitBranchesResponseBody, GitStatusResponseBody } from "../../shared/api.ts";
1
+ import type { GetAllGitBranchesResponseBody, GitStatusResponseBody, IsRepoResponseBody } from "../../shared/api.ts";
2
+ export declare const isRepo: (locales: string[]) => Promise<IsRepoResponseBody>;
2
3
  export declare const getStatus: (locales: string[]) => Promise<GitStatusResponseBody>;
4
+ export declare const fetch: (locales: string[]) => Promise<void>;
3
5
  export declare const stageAllFiles: (locales: string[]) => Promise<void>;
4
6
  export declare const stageAllFilesOfEntity: (locales: string[], entityName: string) => Promise<void>;
5
7
  export declare const stageFileOfEntity: (locales: string[], entityName: string, id: string) => Promise<void>;
6
8
  export declare const unstageAllFiles: (locales: string[]) => Promise<void>;
7
9
  export declare const unstageAllFilesOfEntity: (locales: string[], entityName: string) => Promise<void>;
8
10
  export declare const unstageFileOfEntity: (locales: string[], entityName: string, id: string) => Promise<void>;
11
+ export declare const resetFileOfEntity: (locales: string[], entityName: string, id: string) => Promise<void>;
9
12
  export declare const commitStagedFiles: (locales: string[], message: string) => Promise<void>;
10
13
  export declare const pushCommits: (locales: string[]) => Promise<void>;
11
14
  export declare const pullCommits: (locales: string[]) => Promise<void>;
12
15
  export declare const getBranches: (locales: string[]) => Promise<GetAllGitBranchesResponseBody>;
13
16
  export declare const createBranch: (locales: string[], branchName: string) => Promise<void>;
14
17
  export declare const switchBranch: (locales: string[], branchName: string) => Promise<void>;
18
+ export declare const deleteBranch: (locales: string[], branchName: string) => Promise<void>;
@@ -1,11 +1,14 @@
1
- import { getResource, postResource } from "../utils/api.js";
1
+ import { deleteResource, getResource, postResource } from "../utils/api.js";
2
+ export const isRepo = async (locales) => getResource("/api/git", { locales });
2
3
  export const getStatus = async (locales) => getResource("/api/git/status", { locales });
4
+ export const fetch = async (locales) => postResource("/api/git/fetch", { locales });
3
5
  export const stageAllFiles = async (locales) => postResource("/api/git/stage", { locales });
4
6
  export const stageAllFilesOfEntity = async (locales, entityName) => postResource(`/api/git/stage/${entityName}`, { locales });
5
7
  export const stageFileOfEntity = async (locales, entityName, id) => postResource(`/api/git/stage/${entityName}/${id}`, { locales });
6
8
  export const unstageAllFiles = async (locales) => postResource(`/api/git/unstage`, { locales });
7
9
  export const unstageAllFilesOfEntity = async (locales, entityName) => postResource(`/api/git/unstage/${entityName}`, { locales });
8
10
  export const unstageFileOfEntity = async (locales, entityName, id) => postResource(`/api/git/unstage/${entityName}/${id}`, { locales });
11
+ export const resetFileOfEntity = async (locales, entityName, id) => postResource(`/api/git/reset/${entityName}/${id}`, { locales });
9
12
  export const commitStagedFiles = async (locales, message) => {
10
13
  const body = { message };
11
14
  return postResource(`/api/git/commit`, { locales, body });
@@ -17,4 +20,5 @@ export const createBranch = async (locales, branchName) => {
17
20
  const body = { branchName };
18
21
  return postResource(`/api/git/branch`, { locales, body });
19
22
  };
20
- export const switchBranch = async (locales, branchName) => postResource(`/api/git/branch/${branchName}`, { locales });
23
+ export const switchBranch = async (locales, branchName) => postResource(`/api/git/branch/${encodeURIComponent(branchName)}`, { locales });
24
+ export const deleteBranch = async (locales, branchName) => deleteResource(`/api/git/branch/${encodeURIComponent(branchName)}`, { locales });
@@ -0,0 +1,7 @@
1
+ import type { ComponentChildren, Context } from "preact";
2
+ export type ContextProviderWrapper = <T>(props: {
3
+ children?: ComponentChildren;
4
+ context: Context<T>;
5
+ useValue: () => T;
6
+ }) => ComponentChildren;
7
+ export declare const ContextProviderWrapper: ContextProviderWrapper;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ export const ContextProviderWrapper = props => {
3
+ const { children, context, useValue } = props;
4
+ return _jsx(context.Provider, { value: useValue(), children: children });
5
+ };
@@ -25,6 +25,7 @@ export type InstanceRouteSkeletonOnSubmitHandler = (values: {
25
25
  setInstanceContent: Dispatch<SetStateAction<unknown>>;
26
26
  setCustomId: Dispatch<SetStateAction<string>>;
27
27
  getDeclFromDeclName: GetDeclFromDeclName;
28
+ updateLocalGitState?: () => Promise<void>;
28
29
  }) => Promise<void>;
29
30
  export type InstanceRouteSkeletonTitleBuilder = (values: {
30
31
  locales: string[];
@@ -1,17 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useLocation, useRoute } from "preact-iso";
3
- import { useCallback, useEffect, useState } from "preact/hooks";
3
+ import { useCallback, useContext, useEffect, useState } from "preact/hooks";
4
4
  import { removeAt } from "../../shared/utils/array.js";
5
5
  import { getSerializedDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
6
6
  import { toTitleCase } from "../../shared/utils/string.js";
7
7
  import { validateLocaleIdentifier } from "../../shared/validation/identifier.js";
8
8
  import { deleteInstanceByEntityNameAndId, getChildInstancesForInstanceByEntityName, } from "../api/declarations.js";
9
+ import { GitClientContext } from "../context/gitClient.js";
9
10
  import { useEntityFromRoute } from "../hooks/useEntityFromRoute.js";
10
11
  import { useInstanceNamesByEntity } from "../hooks/useInstanceNamesByEntity.js";
11
12
  import { useGetDeclFromDeclName, } from "../hooks/useSecondaryDeclarations.js";
12
13
  import { useSetting } from "../hooks/useSettings.js";
13
14
  import { homeTitle } from "../routes/Home.js";
14
15
  import { NotFound } from "../routes/NotFound.js";
16
+ import { runWithLoading } from "../signals/loading.js";
15
17
  import { Layout } from "./Layout.js";
16
18
  import { TypeInput } from "./typeInputs/TypeInput.js";
17
19
  import { ValidationErrors } from "./typeInputs/utils/ValidationErrors.js";
@@ -24,6 +26,7 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
24
26
  const [instanceContent, setInstanceContent] = useState();
25
27
  const [childInstances, setChildInstances] = useState([]);
26
28
  const [customId, setCustomId] = useState("");
29
+ const client = useContext(GitClientContext);
27
30
  const { route } = useLocation();
28
31
  useEffect(() => {
29
32
  document.title =
@@ -32,7 +35,7 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
32
35
  }, [entity, id, instanceContent, locales, titleBuilder]);
33
36
  useEffect(() => {
34
37
  if (entity && instanceContent === undefined && declsLoaded) {
35
- init({ locales, entity, instanceId: id, setInstanceContent, getDeclFromDeclName })
38
+ runWithLoading(() => init({ locales, entity, instanceId: id, setInstanceContent, getDeclFromDeclName }))
36
39
  .then(() => id
37
40
  ? getChildInstancesForInstanceByEntityName(locales, entity.name, id).then(result => {
38
41
  setChildInstances(result.instances);
@@ -47,7 +50,7 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
47
50
  event.preventDefault();
48
51
  if (entity && instanceContent !== undefined) {
49
52
  const buttonName = event.submitter?.getAttribute("name") ?? undefined;
50
- onSubmit({
53
+ runWithLoading(() => onSubmit({
51
54
  locales,
52
55
  entity,
53
56
  instanceId: id,
@@ -60,7 +63,8 @@ export const InstanceRouteSkeleton = ({ mode, buttons, init, titleBuilder, onSub
60
63
  setCustomId,
61
64
  setInstanceContent,
62
65
  childInstances,
63
- }).catch((error) => {
66
+ updateLocalGitState: client?.updateLocalState,
67
+ })).catch((error) => {
64
68
  console.error("Error submitting instance data:", error);
65
69
  });
66
70
  }
@@ -0,0 +1 @@
1
+ export declare const LoadingOverlay: () => import("preact").JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { loading } from "../signals/loading.js";
3
+ export const LoadingOverlay = () => (_jsx("div", { class: "loading-overlay" + (loading.value ? " loading-overlay--open" : ""), "aria-hidden": !loading.value, children: "Loading\u2026" }));
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx } from "preact/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useEffect, useRef } from "preact/hooks";
3
- export const ModalDialog = props => {
3
+ import { LoadingOverlay } from "./LoadingOverlay.js";
4
+ export const ModalDialog = ({ children, ...props }) => {
4
5
  const ref = useRef(null);
5
6
  useEffect(() => {
6
7
  if (ref.current) {
@@ -12,5 +13,5 @@ export const ModalDialog = props => {
12
13
  }
13
14
  }
14
15
  }, [props.open]);
15
- return _jsx("dialog", { ...props, open: undefined, ref: ref });
16
+ return (_jsxs("dialog", { ...props, open: undefined, ref: ref, children: [_jsx(LoadingOverlay, {}), children] }));
16
17
  };
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
2
+ import { useContext, useState } from "preact/hooks";
3
+ import { GitContext } from "../../context/git.js";
4
+ import { GitClientContext } from "../../context/gitClient.js";
5
+ import { ModalDialog } from "../ModalDialog.js";
6
+ import { GitBranchManager } from "./GitBranchManager.js";
7
+ import { GitFileManager } from "./GitFileManager.js";
8
+ export const Git = () => {
9
+ const [isOpen, setIsOpen] = useContext(GitContext);
10
+ const [mode, setMode] = useState("files");
11
+ const client = useContext(GitClientContext);
12
+ if (!client || !client.isRepo) {
13
+ return null;
14
+ }
15
+ return (_jsxs(_Fragment, { children: [_jsxs(ModalDialog, { open: isOpen, class: "git", closedBy: "any", onClose: () => {
16
+ setIsOpen(false);
17
+ }, children: [_jsxs("header", { children: [_jsx("h2", { children: (() => {
18
+ switch (mode) {
19
+ case "branches":
20
+ return "Branches";
21
+ case "files":
22
+ return "Files";
23
+ default:
24
+ return null;
25
+ }
26
+ })() }), mode === "branches" ? (_jsx("button", { onClick: () => {
27
+ void client.fetch();
28
+ }, children: "Fetch" })) : null, mode !== "files" ? (_jsx("button", { class: "git__tab git__tab--files", onClick: () => {
29
+ setMode("files");
30
+ }, children: "View Files" })) : null, mode !== "branches" ? (_jsx("button", { class: "git__tab git__tab--branches", onClick: () => {
31
+ setMode("branches");
32
+ }, children: "Manage Branches" })) : null, _jsx("button", { class: "close", onClick: () => {
33
+ setIsOpen(false);
34
+ }, children: "Close" })] }), (() => {
35
+ switch (mode) {
36
+ case "branches":
37
+ return _jsx(GitBranchManager, { client: client });
38
+ case "files":
39
+ return _jsx(GitFileManager, { client: client });
40
+ default:
41
+ return null;
42
+ }
43
+ })()] }), isOpen && mode === "files" ? null : (_jsxs("aside", { class: "git", children: [_jsx("h2", { class: "h1-faded", children: "Version Control" }), _jsx(GitFileManager, { client: client, manageBranches: () => {
44
+ setIsOpen(true);
45
+ setMode("branches");
46
+ } })] }))] }));
47
+ };
@@ -0,0 +1,7 @@
1
+ import type { FunctionComponent } from "preact";
2
+ import type { GitClient } from "../../hooks/useGitClient.ts";
3
+ type Props = {
4
+ client: GitClient;
5
+ };
6
+ export declare const GitBranchManager: FunctionComponent<Props>;
7
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "preact/jsx-runtime";
2
+ export const GitBranchManager = ({ client }) => {
3
+ return (_jsx("div", { class: "git-branches", children: _jsx("ul", { class: "branches", children: client.allBranches.map(branch => {
4
+ const branchInfo = client.branches[branch];
5
+ return (_jsxs("li", { class: [
6
+ "form-row form-row--compact form-row--separated",
7
+ branch === client.currentBranch ? "current" : undefined,
8
+ branchInfo?.remote ? "remote" : undefined,
9
+ ]
10
+ .filter(className => className !== undefined)
11
+ .join(" "), children: [_jsxs("span", { class: "branch__full-name form-row__fill form-row__text", title: branch, children: [branchInfo?.remote ? (_jsxs("span", { class: "branch__origin", children: [branchInfo.remote, "/"] })) : null, _jsx("span", { class: "branch__name", children: branchInfo?.name ?? branch })] }), _jsxs("div", { className: "form-row__group", children: [branch === client.currentBranch ? (_jsx("span", { class: "branch__current-indicator form-row__text", children: " (Current)" })) : null, _jsx("button", { onClick: () => {
12
+ void client.switchBranch(branch);
13
+ }, disabled: branch === client.currentBranch, children: "Switch" }), _jsx("button", { class: "destructive", onClick: () => {
14
+ void client.deleteBranch(branch);
15
+ }, disabled: branch === client.currentBranch || branch.startsWith("remotes/"), children: "Delete" })] })] }, branch));
16
+ }) }) }));
17
+ };
@@ -0,0 +1,17 @@
1
+ import type { ComponentChildren } from "preact";
2
+ import type { InstanceContainerOverview } from "../../../shared/utils/instances.ts";
3
+ export type GitEntityOverview = [
4
+ entityName: string,
5
+ entityNamePlural: string,
6
+ instances: InstanceContainerOverview[]
7
+ ];
8
+ type Props<A extends string> = {
9
+ filesByEntity: GitEntityOverview[];
10
+ fileButtons: {
11
+ label: string;
12
+ action: A;
13
+ }[];
14
+ onFileButtonClick: (entityName: string, instance: InstanceContainerOverview, action: A) => Promise<void>;
15
+ };
16
+ export declare const GitFileList: <A extends string>(props: Props<A>) => ComponentChildren;
17
+ export {};
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ import { useLocation } from "preact-iso";
3
+ import { GitStatusIndicator } from "./GitStatusIndicator.js";
4
+ export const GitFileList = ({ filesByEntity, onFileButtonClick, fileButtons, }) => {
5
+ const { route } = useLocation();
6
+ return filesByEntity.length === 0 ? (_jsx("p", { class: "no-changes", children: "No changes" })) : (_jsx("ul", { class: "git-entity-list", children: filesByEntity.map(([entityName, entityNamePlural, instances]) => (_jsxs("li", { class: "git-entity-list-item", children: [_jsx("span", { class: "title", children: entityNamePlural }), _jsx("ul", { class: "git-instance-list", children: instances.map(instance => (_jsxs("li", { class: "form-row form-row--compact git-instance-list-item", children: [_jsx("span", { class: "title form-row__fill", children: instance.displayName }), _jsx(GitStatusIndicator, { status: instance.gitStatus }), _jsx("button", { onClick: () => {
7
+ route(`/entities/${entityName}/instances/${instance.id}`);
8
+ }, children: "View" }), fileButtons.map(({ label, action }) => (_jsx("button", { onClick: () => {
9
+ void onFileButtonClick(entityName, instance, action);
10
+ }, children: label }, label)))] }, instance.id))) })] }, entityName))) }));
11
+ };
@@ -0,0 +1,8 @@
1
+ import type { FunctionComponent } from "preact";
2
+ import type { GitClient } from "../../hooks/useGitClient.ts";
3
+ type Props = {
4
+ client: GitClient;
5
+ manageBranches?: () => void;
6
+ };
7
+ export declare const GitFileManager: FunctionComponent<Props>;
8
+ export {};
@@ -0,0 +1,34 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "preact/jsx-runtime";
2
+ import { useCallback, useState } from "preact/hooks";
3
+ import { GitFileList } from "./GitFileList.js";
4
+ export const GitFileManager = ({ client, manageBranches }) => {
5
+ const [commitMessage, setCommitMessage] = useState("");
6
+ const commit = () => {
7
+ void client.commit(commitMessage);
8
+ };
9
+ const onCreateBranch = () => {
10
+ const newBranchName = prompt("Enter new branch name:");
11
+ if (newBranchName !== null) {
12
+ void client.createBranch(newBranchName);
13
+ }
14
+ };
15
+ const onSwitchBranch = (event) => {
16
+ void client.switchBranch(event.currentTarget.value);
17
+ };
18
+ const onFileButtonClick = useCallback(async (entityName, instance, action) => {
19
+ switch (action) {
20
+ case "stage":
21
+ return client.stage(entityName, instance);
22
+ case "unstage":
23
+ return client.unstage(entityName, instance);
24
+ case "reset":
25
+ return client.reset(entityName, instance);
26
+ }
27
+ }, [client]);
28
+ return (_jsxs("div", { class: "git-files", children: [_jsxs("div", { class: "form-row form-row--sides", children: [_jsxs("div", { class: "form-row__group", children: [_jsxs("button", { onClick: () => void client.push(), children: ["Push", client.commitsAhead > 0 ? ` (${client.commitsAhead.toString()})` : ""] }), _jsxs("button", { onClick: () => void client.pull(), children: ["Pull", client.commitsBehind > 0 ? ` (${client.commitsBehind.toString()})` : ""] })] }), manageBranches ? _jsx("button", { onClick: manageBranches, children: "Manage Branches" }) : null] }), _jsxs("div", { class: "form-row", children: [_jsx("div", { class: "select-wrapper form-row__fill", children: _jsx("select", { value: client.currentBranch, onInput: onSwitchBranch, children: client.allBranches.map(branch => (_jsx("option", { value: branch, children: branch }, branch))) }) }), _jsx("button", { onClick: onCreateBranch, children: "New branch" })] }), _jsxs("div", { class: "form-row", children: [_jsx("input", { class: "form-row__fill", type: "text", value: commitMessage, onInput: event => {
29
+ setCommitMessage(event.currentTarget.value);
30
+ }, placeholder: "added X to instance Y, \u2026" }), _jsx("button", { onClick: commit, disabled: commitMessage.length === 0 || client.indexFiles.length === 0, children: "Commit" })] }), _jsxs("div", { class: "git-section-title", children: [_jsx("h3", { children: "Files to be committed" }), _jsx("button", { onClick: () => void client.unstageAll(), children: "Unstage all" })] }), _jsx(GitFileList, { filesByEntity: client.indexFiles, fileButtons: [{ label: "Unstage", action: "unstage" }], onFileButtonClick: onFileButtonClick }), _jsxs("div", { class: "git-section-title", children: [_jsx("h3", { children: "Working tree changes" }), _jsx("button", { onClick: () => void client.stageAll(), children: "Stage all" })] }), _jsx(GitFileList, { filesByEntity: client.workingTreeFiles, fileButtons: [
31
+ { label: "Stage", action: "stage" },
32
+ { label: "Reset", action: "reset" },
33
+ ], onFileButtonClick: onFileButtonClick })] }));
34
+ };
@@ -0,0 +1,7 @@
1
+ import type { FunctionComponent } from "preact";
2
+ import { type GitFileStatus } from "../../../shared/utils/git.ts";
3
+ type Props = {
4
+ status: GitFileStatus | undefined;
5
+ };
6
+ export declare const GitStatusIndicator: FunctionComponent<Props>;
7
+ export {};
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { getGitStatusForDisplay, getLabelForGitStatus, } from "../../../shared/utils/git.js";
3
+ export const GitStatusIndicator = ({ status }) => {
4
+ const gitStatusForDisplay = getGitStatusForDisplay(status);
5
+ return (_jsx("span", { class: `git-status git-status--${gitStatusForDisplay ?? ""}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay }));
6
+ };
@@ -12,7 +12,7 @@ export const StringTypeInput = ({ type, value, disabled, onChange }) => {
12
12
  const errors = validateStringConstraints(type, value);
13
13
  return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { class: "editor editor--markdown", children: [_jsxs("div", { class: "textarea-grow-wrap", children: [_jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
14
14
  onChange(event.currentTarget.value);
15
- }, "aria-invalid": errors.length > 0, disabled: disabled }), _jsxs("p", { class: "help", children: ["This textarea supports", " ", _jsx("a", { href: "https://www.markdownguide.org/getting-started/", target: "_blank", rel: "noreferrer", children: "Markdown" }), "."] }), _jsx(MarkdownHighlighting, { class: "textarea-grow-wrap__mirror editor-highlighting", string: value })] }), _jsx(ValidationErrors, { disabled: disabled, errors: errors })] }), _jsx("div", { class: "preview", children: _jsx(Markdown, { string: value, outerHeadingLevel: 2 }) })] })) : (_jsxs("div", { class: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern === undefined
15
+ }, "aria-invalid": errors.length > 0, disabled: disabled }), _jsxs("p", { class: "help", children: ["This textarea supports", " ", _jsx("a", { href: "https://www.markdownguide.org/getting-started/", target: "_blank", rel: "noreferrer", children: "Markdown" }), "."] }), _jsx(MarkdownHighlighting, { class: "textarea-grow-wrap__mirror editor-highlighting", string: value + " " })] }), _jsx(ValidationErrors, { disabled: disabled, errors: errors })] }), _jsx("div", { class: "preview", children: _jsx(Markdown, { string: value, outerHeadingLevel: 2 }) })] })) : (_jsxs("div", { class: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern === undefined
16
16
  ? undefined
17
17
  : pattern.startsWith("^(?:") && pattern.endsWith(")$")
18
18
  ? pattern.slice(4, -2)
@@ -1,9 +1,10 @@
1
1
  import type { SerializedEntityDecl } from "../../shared/schema/declarations/EntityDecl.ts";
2
+ export type EntitySummary = {
3
+ declaration: SerializedEntityDecl;
4
+ instanceCount: number;
5
+ isLocaleEntity: boolean;
6
+ };
2
7
  export declare const EntitiesContext: import("preact").Context<{
3
- entities: {
4
- declaration: SerializedEntityDecl;
5
- instanceCount: number;
6
- isLocaleEntity: boolean;
7
- }[];
8
+ entities: EntitySummary[];
8
9
  reloadEntities: () => Promise<void>;
9
10
  }>;
@@ -1,3 +1,4 @@
1
1
  import type { SetStateAction } from "preact/compat";
2
2
  import type { Dispatch } from "preact/hooks";
3
- export declare const GitContext: import("preact").Context<[isOpen: boolean, setIsOpen: Dispatch<SetStateAction<boolean>>]>;
3
+ export type GitContext = [isOpen: boolean, setIsOpen: Dispatch<SetStateAction<boolean>>];
4
+ export declare const GitContext: import("preact").Context<GitContext>;
@@ -0,0 +1,2 @@
1
+ import type { GitClient } from "../hooks/useGitClient.ts";
2
+ export declare const GitClientContext: import("preact").Context<GitClient | undefined>;
@@ -0,0 +1,2 @@
1
+ import { createContext } from "preact";
2
+ export const GitClientContext = createContext(undefined);
@@ -0,0 +1,37 @@
1
+ import { type GitFileStatus } from "../../shared/utils/git.ts";
2
+ import type { InstanceContainerOverview } from "../../shared/utils/instances.ts";
3
+ import type { GitEntityOverview } from "../components/git/GitFileList.tsx";
4
+ export type GitBranchSummary = {
5
+ current: boolean;
6
+ name: string;
7
+ commit: string;
8
+ label: string;
9
+ linkedWorkTree: boolean;
10
+ remote?: string;
11
+ };
12
+ export type GitClient = {
13
+ isRepo: boolean;
14
+ commitsAhead: number;
15
+ commitsBehind: number;
16
+ indexFiles: GitEntityOverview[];
17
+ workingTreeFiles: GitEntityOverview[];
18
+ allBranches: string[];
19
+ currentBranch: string;
20
+ branches: Record<string, GitBranchSummary>;
21
+ isDetached: boolean;
22
+ updateLocalState: () => Promise<void>;
23
+ getGitStatusOfInstance: (entityName: string, instanceId: string) => GitFileStatus | undefined;
24
+ fetch: () => Promise<void>;
25
+ stage: (entityName: string, instance: InstanceContainerOverview) => Promise<void>;
26
+ stageAll: (entityName?: string) => Promise<void>;
27
+ unstage: (entityName: string, instance: InstanceContainerOverview) => Promise<void>;
28
+ unstageAll: (entityName?: string) => Promise<void>;
29
+ reset: (entityName: string, instance: InstanceContainerOverview) => Promise<void>;
30
+ commit: (commitMessage: string) => Promise<void>;
31
+ push: () => Promise<void>;
32
+ pull: () => Promise<void>;
33
+ createBranch: (newBranchName: string) => Promise<void>;
34
+ switchBranch: (targetBranch: string) => Promise<void>;
35
+ deleteBranch: (targetBranch: string) => Promise<void>;
36
+ };
37
+ export declare const useGitClient: () => GitClient;