tsondb 0.7.6 → 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 (74) hide show
  1. package/dist/src/node/schema/Node.d.ts +8 -1
  2. package/dist/src/node/schema/Node.js +34 -18
  3. package/dist/src/node/schema/Schema.js +14 -10
  4. package/dist/src/node/schema/TypeParameter.js +1 -1
  5. package/dist/src/node/schema/declarations/EntityDecl.js +1 -1
  6. package/dist/src/node/schema/declarations/EnumDecl.js +1 -1
  7. package/dist/src/node/schema/declarations/TypeAliasDecl.js +1 -1
  8. package/dist/src/node/schema/types/generic/ArrayType.js +1 -1
  9. package/dist/src/node/schema/types/generic/EnumType.js +1 -1
  10. package/dist/src/node/schema/types/generic/ObjectType.js +1 -1
  11. package/dist/src/node/schema/types/references/ChildEntitiesType.js +1 -1
  12. package/dist/src/node/schema/types/references/IncludeIdentifierType.js +2 -2
  13. package/dist/src/node/schema/types/references/NestedEntityMapType.d.ts +12 -2
  14. package/dist/src/node/schema/types/references/NestedEntityMapType.js +2 -2
  15. package/dist/src/node/schema/types/references/ReferenceIdentifierType.js +2 -2
  16. package/dist/src/node/server/api/git.js +156 -24
  17. package/dist/src/node/server/index.js +4 -0
  18. package/dist/src/node/utils/instanceOperations.d.ts +1 -1
  19. package/dist/src/node/utils/instanceOperations.js +2 -2
  20. package/dist/src/shared/api.d.ts +17 -1
  21. package/dist/src/shared/schema/declarations/EntityDecl.d.ts +2 -0
  22. package/dist/src/shared/schema/declarations/EntityDecl.js +2 -0
  23. package/dist/src/shared/schema/declarations/EnumDecl.d.ts +2 -1
  24. package/dist/src/shared/schema/declarations/EnumDecl.js +2 -0
  25. package/dist/src/shared/schema/declarations/TypeAliasDecl.d.ts +2 -1
  26. package/dist/src/shared/schema/declarations/TypeAliasDecl.js +2 -1
  27. package/dist/src/shared/utils/git.d.ts +4 -0
  28. package/dist/src/shared/utils/git.js +6 -1
  29. package/dist/src/shared/utils/markdown.d.ts +36 -17
  30. package/dist/src/shared/utils/markdown.js +79 -23
  31. package/dist/src/shared/utils/object.d.ts +1 -0
  32. package/dist/src/shared/utils/object.js +1 -0
  33. package/dist/src/web/api/git.d.ts +5 -1
  34. package/dist/src/web/api/git.js +6 -2
  35. package/dist/src/web/components/ContextProviderWrapper.d.ts +7 -0
  36. package/dist/src/web/components/ContextProviderWrapper.js +5 -0
  37. package/dist/src/web/components/InstanceRouteSkeleton.d.ts +6 -0
  38. package/dist/src/web/components/InstanceRouteSkeleton.js +12 -8
  39. package/dist/src/web/components/LoadingOverlay.d.ts +1 -0
  40. package/dist/src/web/components/LoadingOverlay.js +3 -0
  41. package/dist/src/web/components/ModalDialog.js +4 -3
  42. package/dist/src/web/components/git/Git.js +47 -0
  43. package/dist/src/web/components/git/GitBranchManager.d.ts +7 -0
  44. package/dist/src/web/components/git/GitBranchManager.js +17 -0
  45. package/dist/src/web/components/git/GitFileList.d.ts +17 -0
  46. package/dist/src/web/components/git/GitFileList.js +11 -0
  47. package/dist/src/web/components/git/GitFileManager.d.ts +8 -0
  48. package/dist/src/web/components/git/GitFileManager.js +34 -0
  49. package/dist/src/web/components/git/GitStatusIndicator.d.ts +7 -0
  50. package/dist/src/web/components/git/GitStatusIndicator.js +6 -0
  51. package/dist/src/web/components/typeInputs/StringTypeInput.js +1 -1
  52. package/dist/src/web/components/typeInputs/utils/MismatchingTypeError.js +2 -2
  53. package/dist/src/web/context/entities.d.ts +6 -5
  54. package/dist/src/web/context/git.d.ts +2 -1
  55. package/dist/src/web/context/gitClient.d.ts +2 -0
  56. package/dist/src/web/context/gitClient.js +2 -0
  57. package/dist/src/web/hooks/useGitClient.d.ts +37 -0
  58. package/dist/src/web/hooks/useGitClient.js +313 -0
  59. package/dist/src/web/hooks/useSecondaryDeclarations.js +6 -3
  60. package/dist/src/web/hooks/useSettings.js +0 -1
  61. package/dist/src/web/index.js +6 -2
  62. package/dist/src/web/routes/CreateInstance.js +29 -23
  63. package/dist/src/web/routes/Entity.js +10 -4
  64. package/dist/src/web/routes/Instance.js +21 -8
  65. package/dist/src/web/signals/loading.d.ts +2 -0
  66. package/dist/src/web/signals/loading.js +11 -0
  67. package/dist/src/web/utils/BlockMarkdown.js +1 -1
  68. package/dist/src/web/utils/debug.d.ts +1 -0
  69. package/dist/src/web/utils/debug.js +4 -0
  70. package/dist/src/web/utils/typeSkeleton.js +1 -1
  71. package/package.json +3 -3
  72. package/public/css/styles.css +132 -70
  73. package/dist/src/web/components/Git.js +0 -164
  74. /package/dist/src/web/components/{Git.d.ts → git/Git.d.ts} +0 -0
@@ -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,4 +1,4 @@
1
- import { jsxs as _jsxs } from "preact/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  export const MismatchingTypeError = ({ expected, actual }) => {
3
- return (_jsxs("div", { role: "alert", children: ["Expected value of type ", expected, ", but got ", JSON.stringify(actual)] }));
3
+ return (_jsxs("div", { role: "alert", children: ["Expected value of type ", expected, ", but got", " ", _jsx("code", { children: actual === undefined ? "undefined" : JSON.stringify(actual) })] }));
4
4
  };
@@ -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;
@@ -0,0 +1,313 @@
1
+ import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks";
2
+ import { isSerializedEntityDecl, isSerializedEntityDeclWithParentReference, } from "../../shared/schema/declarations/EntityDecl.js";
3
+ import { isChangedInIndex, isChangedInWorkingDir, splitBranchName, } from "../../shared/utils/git.js";
4
+ import { deleteInstanceByEntityNameAndId } from "../api/declarations.js";
5
+ import * as GitApi from "../api/git.js";
6
+ import { EntitiesContext } from "../context/entities.js";
7
+ import { runWithLoading } from "../signals/loading.js";
8
+ import { logAndAlertError } from "../utils/debug.js";
9
+ import { useSetting } from "./useSettings.js";
10
+ const filterFilesForDisplay = (predicate, entities, data) => Object.entries(data.instances)
11
+ .map(([entityName, instances]) => [
12
+ entityName,
13
+ entities.find(entity => entity.declaration.name === entityName)?.declaration.namePlural ??
14
+ entityName,
15
+ instances
16
+ .filter(instance => instance.gitStatus !== undefined && predicate(instance.gitStatus))
17
+ .sort((a, b) => a.displayName.localeCompare(b.displayName, undefined, { numeric: true })),
18
+ ])
19
+ .filter(([_1, _2, instances]) => instances.length > 0)
20
+ .sort((a, b) => a[1].localeCompare(b[1]));
21
+ export const useGitClient = () => {
22
+ const [locales] = useSetting("displayedLocales");
23
+ const { entities } = useContext(EntitiesContext);
24
+ const [isRepo, setIsRepo] = useState(false);
25
+ const [commitsAhead, setCommitsAhead] = useState(0);
26
+ const [commitsBehind, setCommitsBehind] = useState(0);
27
+ const [indexFiles, setIndexFiles] = useState([]);
28
+ const [workingTreeFiles, setWorkingTreeFiles] = useState([]);
29
+ const [allBranches, setAllBranches] = useState([]);
30
+ const [currentBranch, setCurrentBranch] = useState("");
31
+ const [branches, setBranches] = useState({});
32
+ const [isDetached, setIsDetached] = useState(false);
33
+ const updateGitStatus = useCallback(async () => {
34
+ const { isRepo } = await GitApi.isRepo(locales);
35
+ setIsRepo(isRepo);
36
+ if (isRepo && entities.length > 0) {
37
+ try {
38
+ const [statusData, branchesData] = await Promise.all([
39
+ GitApi.getStatus(locales),
40
+ GitApi.getBranches(locales),
41
+ ]);
42
+ setIndexFiles(filterFilesForDisplay(isChangedInIndex, entities, statusData));
43
+ setWorkingTreeFiles(filterFilesForDisplay(isChangedInWorkingDir, entities, statusData));
44
+ setCommitsAhead(statusData.commitsAhead);
45
+ setCommitsBehind(statusData.commitsBehind);
46
+ setAllBranches(branchesData.allBranches);
47
+ setCurrentBranch(branchesData.currentBranch);
48
+ setBranches(Object.fromEntries(Object.entries(branchesData.branches).map(([branch, branchSummary]) => {
49
+ const { remote, name } = splitBranchName(branch);
50
+ return [
51
+ branch,
52
+ {
53
+ ...branchSummary,
54
+ remote,
55
+ name,
56
+ },
57
+ ];
58
+ })));
59
+ setIsDetached(branchesData.isDetached);
60
+ }
61
+ catch (error) {
62
+ logAndAlertError(error, "Error updating git status: ");
63
+ }
64
+ }
65
+ }, [entities, locales]);
66
+ useEffect(() => {
67
+ void updateGitStatus();
68
+ }, [updateGitStatus]);
69
+ const getGitStatusOfInstance = useCallback((entityName, instanceId) => {
70
+ const entity = indexFiles.find(([name]) => name === entityName);
71
+ const instanceInIndex = entity?.[2].find(instance => instance.id === instanceId);
72
+ if (instanceInIndex?.gitStatus !== undefined) {
73
+ return instanceInIndex.gitStatus;
74
+ }
75
+ const workingTreeEntity = workingTreeFiles.find(([name]) => name === entityName);
76
+ const instanceInWorkingTree = workingTreeEntity?.[2].find(instance => instance.id === instanceId);
77
+ return instanceInWorkingTree?.gitStatus;
78
+ }, [indexFiles, workingTreeFiles]);
79
+ const fetch = useCallback(async () => {
80
+ await runWithLoading(async () => {
81
+ try {
82
+ await GitApi.fetch(locales);
83
+ await updateGitStatus();
84
+ }
85
+ catch (error) {
86
+ logAndAlertError(error, "Error fetching from remote: ");
87
+ }
88
+ });
89
+ }, [locales, updateGitStatus]);
90
+ const stage = useCallback(async (entityName, instance) => {
91
+ await runWithLoading(async () => {
92
+ try {
93
+ await GitApi.stageFileOfEntity(locales, entityName, instance.id);
94
+ await updateGitStatus();
95
+ }
96
+ catch (error) {
97
+ logAndAlertError(error, "Error staging instance: ");
98
+ }
99
+ });
100
+ }, [locales, updateGitStatus]);
101
+ const stageAll = useCallback(async (entityName) => {
102
+ await runWithLoading(async () => {
103
+ try {
104
+ if (entityName) {
105
+ await GitApi.stageAllFilesOfEntity(locales, entityName);
106
+ }
107
+ else {
108
+ await GitApi.stageAllFiles(locales);
109
+ }
110
+ await updateGitStatus();
111
+ }
112
+ catch (error) {
113
+ logAndAlertError(error, "Error staging all instances: ");
114
+ }
115
+ });
116
+ }, [locales, updateGitStatus]);
117
+ const unstage = useCallback(async (entityName, instance) => {
118
+ await runWithLoading(async () => {
119
+ try {
120
+ await GitApi.unstageFileOfEntity(locales, entityName, instance.id);
121
+ await updateGitStatus();
122
+ }
123
+ catch (error) {
124
+ logAndAlertError(error, "Error unstaging instance: ");
125
+ }
126
+ });
127
+ }, [locales, updateGitStatus]);
128
+ const unstageAll = useCallback(async (entityName) => {
129
+ await runWithLoading(async () => {
130
+ try {
131
+ if (entityName) {
132
+ await GitApi.unstageAllFilesOfEntity(locales, entityName);
133
+ }
134
+ else {
135
+ await GitApi.unstageAllFiles(locales);
136
+ }
137
+ await updateGitStatus();
138
+ }
139
+ catch (error) {
140
+ logAndAlertError(error, "Error unstaging all instances: ");
141
+ }
142
+ });
143
+ }, [locales, updateGitStatus]);
144
+ const reset = useCallback(async (entityName, instance) => {
145
+ await runWithLoading(async () => {
146
+ if (!confirm(`Are you sure you want to reset instance "${instance.displayName}" (${instance.id})?`)) {
147
+ return;
148
+ }
149
+ const entity = entities.find(e => e.declaration.name === entityName)?.declaration;
150
+ if (instance.gitStatus?.workingDir === "D" &&
151
+ entity &&
152
+ isSerializedEntityDecl(entity) &&
153
+ isSerializedEntityDeclWithParentReference(entity) &&
154
+ !confirm(`If you deleted the parent of "${instance.displayName}" (${instance.id}) before, make sure to restore it as well. Continue?`)) {
155
+ return;
156
+ }
157
+ try {
158
+ if (instance.gitStatus?.workingDir === "?") {
159
+ await deleteInstanceByEntityNameAndId(locales, entityName, instance.id);
160
+ }
161
+ else {
162
+ await GitApi.resetFileOfEntity(locales, entityName, instance.id);
163
+ }
164
+ await updateGitStatus();
165
+ }
166
+ catch (error) {
167
+ logAndAlertError(error, "Error resetting instance: ");
168
+ }
169
+ });
170
+ }, [entities, locales, updateGitStatus]);
171
+ const commit = useCallback(async (commitMessage) => {
172
+ await runWithLoading(async () => {
173
+ if (commitMessage.length > 0 &&
174
+ indexFiles.length > 0 &&
175
+ confirm("Do you want to commit all staged files?")) {
176
+ try {
177
+ await GitApi.commitStagedFiles(locales, commitMessage);
178
+ await updateGitStatus();
179
+ }
180
+ catch (error) {
181
+ logAndAlertError(error, "Error committing instances: ");
182
+ }
183
+ }
184
+ });
185
+ }, [indexFiles.length, locales, updateGitStatus]);
186
+ const push = useCallback(async () => {
187
+ await runWithLoading(async () => {
188
+ try {
189
+ await GitApi.pushCommits(locales);
190
+ alert("Pushed commits successfully");
191
+ await updateGitStatus();
192
+ }
193
+ catch (error) {
194
+ logAndAlertError(error, "Error pushing commits: ");
195
+ }
196
+ });
197
+ }, [locales, updateGitStatus]);
198
+ const pull = useCallback(async () => {
199
+ await runWithLoading(async () => {
200
+ try {
201
+ await GitApi.pullCommits(locales);
202
+ alert("Pulled commits successfully");
203
+ await updateGitStatus();
204
+ }
205
+ catch (error) {
206
+ logAndAlertError(error, "Error pulling commits: ");
207
+ }
208
+ });
209
+ }, [locales, updateGitStatus]);
210
+ const createBranch = useCallback(async (newBranchName) => {
211
+ await runWithLoading(async () => {
212
+ if (newBranchName.length === 0) {
213
+ alert("Branch name cannot be empty");
214
+ return;
215
+ }
216
+ if (allBranches.includes(newBranchName)) {
217
+ alert("Branch name already exists");
218
+ return;
219
+ }
220
+ try {
221
+ await GitApi.createBranch(locales, newBranchName);
222
+ alert(`Created branch "${newBranchName}" successfully`);
223
+ await updateGitStatus();
224
+ }
225
+ catch (error) {
226
+ logAndAlertError(error, "Error creating branch: ");
227
+ }
228
+ });
229
+ }, [allBranches, locales, updateGitStatus]);
230
+ const switchBranch = useCallback(async (targetBranch) => {
231
+ await runWithLoading(async () => {
232
+ try {
233
+ await GitApi.switchBranch(locales, targetBranch);
234
+ await updateGitStatus();
235
+ }
236
+ catch (error) {
237
+ logAndAlertError(error, "Error switching branch: ");
238
+ }
239
+ });
240
+ }, [locales, updateGitStatus]);
241
+ const deleteBranch = useCallback(async (targetBranch) => {
242
+ await runWithLoading(async () => {
243
+ if (targetBranch === currentBranch) {
244
+ alert("Cannot delete the current branch");
245
+ return;
246
+ }
247
+ if (!allBranches.includes(targetBranch)) {
248
+ alert(`Branch "${targetBranch}" does not exist`);
249
+ return;
250
+ }
251
+ if (!confirm(`Are you sure you want to delete branch "${targetBranch}"?`)) {
252
+ return;
253
+ }
254
+ try {
255
+ await GitApi.deleteBranch(locales, targetBranch);
256
+ alert(`Deleted branch "${targetBranch}" successfully`);
257
+ await updateGitStatus();
258
+ }
259
+ catch (error) {
260
+ logAndAlertError(error, "Error deleting branch: ");
261
+ }
262
+ });
263
+ }, [allBranches, currentBranch, locales, updateGitStatus]);
264
+ return useMemo(() => ({
265
+ isRepo,
266
+ commitsAhead,
267
+ commitsBehind,
268
+ indexFiles,
269
+ workingTreeFiles,
270
+ allBranches,
271
+ currentBranch,
272
+ branches,
273
+ isDetached,
274
+ getGitStatusOfInstance,
275
+ updateLocalState: updateGitStatus,
276
+ fetch,
277
+ stage,
278
+ stageAll,
279
+ unstage,
280
+ unstageAll,
281
+ reset,
282
+ commit,
283
+ push,
284
+ pull,
285
+ createBranch,
286
+ switchBranch,
287
+ deleteBranch,
288
+ }), [
289
+ allBranches,
290
+ branches,
291
+ commit,
292
+ commitsAhead,
293
+ commitsBehind,
294
+ createBranch,
295
+ currentBranch,
296
+ deleteBranch,
297
+ fetch,
298
+ getGitStatusOfInstance,
299
+ indexFiles,
300
+ isDetached,
301
+ isRepo,
302
+ pull,
303
+ push,
304
+ reset,
305
+ stage,
306
+ stageAll,
307
+ switchBranch,
308
+ unstage,
309
+ unstageAll,
310
+ updateGitStatus,
311
+ workingTreeFiles,
312
+ ]);
313
+ };
@@ -1,4 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "preact/hooks";
2
+ import { isSerializedEntityDeclWithParentReference } from "../../shared/schema/declarations/EntityDecl.js";
3
+ import { isSerializedEnumDecl } from "../../shared/schema/declarations/EnumDecl.js";
4
+ import { isSerializedTypeAliasDecl } from "../../shared/schema/declarations/TypeAliasDecl.js";
2
5
  import { getAllDeclarations } from "../api/declarations.js";
3
6
  import { useSetting } from "./useSettings.js";
4
7
  export const useGetDeclFromDeclName = () => {
@@ -9,9 +12,9 @@ export const useGetDeclFromDeclName = () => {
9
12
  .then(data => {
10
13
  setSecondaryDeclarations(data.declarations
11
14
  .map(decl => decl.declaration)
12
- .filter((decl) => decl.kind === "EnumDecl" ||
13
- decl.kind === "TypeAliasDecl" ||
14
- decl.parentReferenceKey !== undefined));
15
+ .filter(decl => isSerializedEnumDecl(decl) ||
16
+ isSerializedTypeAliasDecl(decl) ||
17
+ isSerializedEntityDeclWithParentReference(decl)));
15
18
  })
16
19
  .catch((error) => {
17
20
  if (error instanceof Error) {
@@ -9,7 +9,6 @@ const defaultSettingsFromConfig = (config) => ({
9
9
  displayedLocales: config.defaultLocales.length > 0 ? config.defaultLocales : defaultSettings.displayedLocales,
10
10
  });
11
11
  export const useSettings = (config) => {
12
- console.log("useSettings");
13
12
  const [settings, setSettings] = useState(() => Object.fromEntries(Object.entries(defaultSettingsFromConfig(config)).map(([key, initialValue]) => {
14
13
  const item = localStorage.getItem(key);
15
14
  if (item) {
@@ -4,11 +4,15 @@ import { LocationProvider, Route, Router, useLocation } from "preact-iso";
4
4
  import { useEffect, useState } from "preact/hooks";
5
5
  import { getAllEntities } from "./api/declarations.js";
6
6
  import { getWebConfig } from "./api/index.js";
7
- import { Git } from "./components/Git.js";
7
+ import { ContextProviderWrapper } from "./components/ContextProviderWrapper.js";
8
+ import { Git } from "./components/git/Git.js";
9
+ import { LoadingOverlay } from "./components/LoadingOverlay.js";
8
10
  import { ConfigContext } from "./context/config.js";
9
11
  import { EntitiesContext } from "./context/entities.js";
10
12
  import { GitContext } from "./context/git.js";
13
+ import { GitClientContext } from "./context/gitClient.js";
11
14
  import { SettingsContext } from "./context/settings.js";
15
+ import { useGitClient } from "./hooks/useGitClient.js";
12
16
  import { useMappedAPIResource } from "./hooks/useMappedAPIResource.js";
13
17
  import { useSettings } from "./hooks/useSettings.js";
14
18
  import { CreateInstance } from "./routes/CreateInstance.js";
@@ -29,7 +33,7 @@ const App = ({ config }) => {
29
33
  alert("Error reloading entities: " + String(error));
30
34
  });
31
35
  }, [location.path, reloadEntities]);
32
- return (_jsx(ConfigContext.Provider, { value: config, children: _jsx(SettingsContext.Provider, { value: settingsContext, children: _jsx(GitContext.Provider, { value: [isGitOpen, setIsGitOpen], children: _jsx(LocationProvider, { children: _jsxs(EntitiesContext.Provider, { value: { entities: entities ?? [], reloadEntities }, children: [_jsxs(Router, { children: [_jsx(Route, { path: "/", component: Home }), _jsx(Route, { path: "/entities/:name", component: Entity }), _jsx(Route, { path: "/entities/:name/instances/create", component: CreateInstance }), _jsx(Route, { path: "/entities/:name/instances/:id", component: Instance }), _jsx(Route, { default: true, component: NotFound })] }), _jsx(Git, {})] }) }) }) }) }));
36
+ return (_jsx(ConfigContext.Provider, { value: config, children: _jsx(SettingsContext.Provider, { value: settingsContext, children: _jsx(GitContext.Provider, { value: [isGitOpen, setIsGitOpen], children: _jsx(LocationProvider, { children: _jsx(EntitiesContext.Provider, { value: { entities: entities ?? [], reloadEntities }, children: _jsxs(ContextProviderWrapper, { context: GitClientContext, useValue: useGitClient, children: [_jsx(LoadingOverlay, {}), _jsxs(Router, { children: [_jsx(Route, { path: "/", component: Home }), _jsx(Route, { path: "/entities/:name", component: Entity }), _jsx(Route, { path: "/entities/:name/instances/create", component: CreateInstance }), _jsx(Route, { path: "/entities/:name/instances/:id", component: Instance }), _jsx(Route, { default: true, component: NotFound })] }), _jsx(Git, {})] }) }) }) }) }) }));
33
37
  };
34
38
  const config = await getWebConfig();
35
39
  const root = document.getElementById("app");