tsondb 0.5.8 → 0.5.12

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 (68) hide show
  1. package/dist/src/bin/tsondb.d.ts +1 -9
  2. package/dist/src/bin/tsondb.js +7 -0
  3. package/dist/src/index.d.ts +1 -1
  4. package/dist/src/node/index.d.ts +2 -2
  5. package/dist/src/node/index.js +8 -2
  6. package/dist/src/node/renderers/jsonschema/index.d.ts +1 -1
  7. package/dist/src/node/renderers/ts/index.d.ts +1 -1
  8. package/dist/src/node/{Schema.d.ts → schema/Schema.d.ts} +2 -2
  9. package/dist/src/node/{Schema.js → schema/Schema.js} +10 -7
  10. package/dist/src/node/schema/declarations/EntityDecl.d.ts +31 -30
  11. package/dist/src/node/schema/declarations/EntityDecl.js +1 -0
  12. package/dist/src/node/server/api/declarations.js +3 -4
  13. package/dist/src/node/server/api/git.js +1 -2
  14. package/dist/src/node/server/api/instances.js +2 -3
  15. package/dist/src/node/server/index.d.ts +7 -2
  16. package/dist/src/node/server/index.js +10 -10
  17. package/dist/src/node/server/init.d.ts +1 -1
  18. package/dist/src/node/server/init.js +10 -0
  19. package/dist/src/node/utils/displayName.d.ts +3 -0
  20. package/dist/src/node/utils/displayName.js +19 -0
  21. package/dist/src/shared/config.d.ts +11 -0
  22. package/dist/src/{node/renderers/Output.d.ts → shared/output.d.ts} +1 -1
  23. package/dist/src/shared/output.js +1 -0
  24. package/dist/src/shared/utils/compare.js +6 -1
  25. package/dist/src/shared/utils/displayName.d.ts +1 -1
  26. package/dist/src/shared/utils/displayName.js +1 -1
  27. package/dist/src/shared/utils/instances.d.ts +3 -2
  28. package/dist/src/shared/utils/instances.js +3 -3
  29. package/dist/src/shared/utils/markdown.js +1 -1
  30. package/dist/src/web/components/Git.js +1 -1
  31. package/dist/src/web/components/typeInputs/ArrayTypeInput.d.ts +2 -1
  32. package/dist/src/web/components/typeInputs/ArrayTypeInput.js +10 -6
  33. package/dist/src/web/components/typeInputs/BooleanTypeInput.d.ts +1 -1
  34. package/dist/src/web/components/typeInputs/BooleanTypeInput.js +4 -0
  35. package/dist/src/web/components/typeInputs/DateTypeInput.d.ts +1 -1
  36. package/dist/src/web/components/typeInputs/DateTypeInput.js +4 -0
  37. package/dist/src/web/components/typeInputs/EnumTypeInput.d.ts +1 -0
  38. package/dist/src/web/components/typeInputs/EnumTypeInput.js +2 -2
  39. package/dist/src/web/components/typeInputs/FloatTypeInput.d.ts +1 -1
  40. package/dist/src/web/components/typeInputs/FloatTypeInput.js +5 -1
  41. package/dist/src/web/components/typeInputs/IncludeIdentifierTypeInput.d.ts +1 -0
  42. package/dist/src/web/components/typeInputs/IncludeIdentifierTypeInput.js +2 -2
  43. package/dist/src/web/components/typeInputs/IntegerTypeInput.d.ts +1 -1
  44. package/dist/src/web/components/typeInputs/IntegerTypeInput.js +5 -1
  45. package/dist/src/web/components/typeInputs/NestedEntityMapTypeInput.d.ts +2 -1
  46. package/dist/src/web/components/typeInputs/NestedEntityMapTypeInput.js +21 -12
  47. package/dist/src/web/components/typeInputs/ObjectTypeInput.d.ts +2 -1
  48. package/dist/src/web/components/typeInputs/ObjectTypeInput.js +9 -4
  49. package/dist/src/web/components/typeInputs/ReferenceIdentifierTypeInput.d.ts +1 -1
  50. package/dist/src/web/components/typeInputs/ReferenceIdentifierTypeInput.js +5 -1
  51. package/dist/src/web/components/typeInputs/StringTypeInput.d.ts +1 -1
  52. package/dist/src/web/components/typeInputs/StringTypeInput.js +11 -3
  53. package/dist/src/web/components/typeInputs/TypeInput.d.ts +3 -2
  54. package/dist/src/web/components/typeInputs/TypeInput.js +16 -58
  55. package/dist/src/web/hooks/useInstanceNamesByEntity.d.ts +1 -1
  56. package/dist/src/web/hooks/useInstanceNamesByEntity.js +1 -1
  57. package/dist/src/web/hooks/useSecondaryDeclarations.d.ts +1 -1
  58. package/dist/src/web/hooks/useSecondaryDeclarations.js +3 -3
  59. package/dist/src/web/routes/CreateInstance.js +27 -13
  60. package/dist/src/web/routes/Entity.js +27 -13
  61. package/dist/src/web/routes/Home.js +15 -1
  62. package/dist/src/web/routes/Instance.js +27 -12
  63. package/dist/src/web/routes/NotFound.js +4 -0
  64. package/dist/src/web/utils/debug.d.ts +1 -0
  65. package/dist/src/web/utils/debug.js +5 -0
  66. package/package.json +6 -6
  67. package/public/css/styles.css +132 -38
  68. /package/dist/src/{node/renderers/Output.js → shared/config.js} +0 -0
@@ -1,9 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useState } from "preact/hooks";
3
3
  import { validateNumberConstraints } from "../../../shared/validation/number.js";
4
+ import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
4
5
  import { ValidationErrors } from "./utils/ValidationErrors.js";
5
6
  export const FloatTypeInput = ({ type, value, onChange }) => {
6
- const [stringValue, setStringValue] = useState(value.toString());
7
+ const [stringValue, setStringValue] = useState(typeof value === "number" ? value.toString() : "");
8
+ if (typeof value !== "number") {
9
+ return _jsx(MismatchingTypeError, { expected: "float", actual: value });
10
+ }
7
11
  const errors = validateNumberConstraints(type, value);
8
12
  return (_jsxs("div", { class: "field", children: [_jsx("input", { type: "number", value: stringValue, onInput: event => {
9
13
  setStringValue(event.currentTarget.value);
@@ -4,6 +4,7 @@ import type { InstanceNamesByEntity } from "../../hooks/useInstanceNamesByEntity
4
4
  import type { GetDeclFromDeclName } from "../../hooks/useSecondaryDeclarations.ts";
5
5
  type Props = {
6
6
  type: SerializedIncludeIdentifierType;
7
+ path: string | undefined;
7
8
  value: unknown;
8
9
  instanceNamesByEntity: InstanceNamesByEntity;
9
10
  getDeclFromDeclName: GetDeclFromDeclName;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { TypeInput } from "./TypeInput.js";
3
- export const IncludeIdentifierTypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
3
+ export const IncludeIdentifierTypeInput = ({ type, path, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
4
4
  const decl = getDeclFromDeclName(type.reference);
5
5
  if (decl === undefined) {
6
6
  return (_jsxs("div", { role: "alert", children: ["Unresolved declaration identifier ", _jsx("code", { children: type.reference })] }));
7
7
  }
8
- return (_jsx(TypeInput, { type: decl.type, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
8
+ return (_jsx(TypeInput, { type: decl.type, path: path, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
9
9
  };
@@ -2,7 +2,7 @@ import type { FunctionComponent } from "preact";
2
2
  import type { SerializedIntegerType } from "../../../node/schema/types/primitives/IntegerType.ts";
3
3
  type Props = {
4
4
  type: SerializedIntegerType;
5
- value: number;
5
+ value: unknown;
6
6
  onChange: (value: number) => void;
7
7
  };
8
8
  export declare const IntegerTypeInput: FunctionComponent<Props>;
@@ -1,9 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useState } from "preact/hooks";
3
3
  import { validateNumberConstraints } from "../../../shared/validation/number.js";
4
+ import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
4
5
  import { ValidationErrors } from "./utils/ValidationErrors.js";
5
6
  export const IntegerTypeInput = ({ type, value, onChange }) => {
6
- const [stringValue, setStringValue] = useState(value.toString());
7
+ const [stringValue, setStringValue] = useState(typeof value === "number" ? value.toString() : "");
8
+ if (typeof value !== "number") {
9
+ return _jsx(MismatchingTypeError, { expected: "float", actual: value });
10
+ }
7
11
  const errors = validateNumberConstraints(type, value);
8
12
  return (_jsxs("div", { class: "field", children: [_jsx("input", { type: "number", value: stringValue, onInput: event => {
9
13
  setStringValue(event.currentTarget.value);
@@ -4,7 +4,8 @@ import type { InstanceNamesByEntity } from "../../hooks/useInstanceNamesByEntity
4
4
  import type { GetDeclFromDeclName } from "../../hooks/useSecondaryDeclarations.ts";
5
5
  type Props = {
6
6
  type: SerializedNestedEntityMapType;
7
- value: Record<string, unknown>;
7
+ path: string | undefined;
8
+ value: unknown;
8
9
  instanceNamesByEntity: InstanceNamesByEntity;
9
10
  getDeclFromDeclName: GetDeclFromDeclName;
10
11
  onChange: (value: Record<string, unknown>) => void;
@@ -1,30 +1,39 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useState } from "preact/hooks";
3
3
  import { sortObjectKeysAlphabetically } from "../../../shared/utils/object.js";
4
+ import { toTitleCase } from "../../../shared/utils/string.js";
4
5
  import { createTypeSkeleton } from "../../utils/typeSkeleton.js";
5
6
  import { Select } from "../Select.js";
6
7
  import { TypeInput } from "./TypeInput.js";
7
- export const NestedEntityMapTypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
8
+ import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
9
+ export const NestedEntityMapTypeInput = ({ type, path, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
8
10
  const [newKey, setNewKey] = useState("");
11
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
12
+ return _jsx(MismatchingTypeError, { expected: "object", actual: value });
13
+ }
9
14
  const existingKeys = Object.keys(value);
10
15
  const secondaryInstances = (instanceNamesByEntity[type.secondaryEntity] ?? [])
11
16
  .slice()
12
17
  .filter(instance => !existingKeys.includes(instance.id))
13
- .sort((a, b) => a.name.localeCompare(b.name));
14
- return (_jsxs("div", { class: "field field--container field--nestedentitymap", children: [existingKeys.length > 0 && (_jsx("ul", { children: Object.entries(value).map(([key, item]) => (_jsxs("li", { class: "container-item dict-item", children: [_jsxs("div", { className: "container-item-header", children: [_jsx("div", { className: "container-item-title", children: _jsxs("span", { children: [_jsx("strong", { children: instanceNamesByEntity[type.secondaryEntity]?.find(instance => instance.id === key)?.name ?? key }), " ", _jsx("span", { className: "id", children: key })] }) }), _jsx("div", { className: "btns", children: _jsx("button", { class: "destructive", onClick: () => {
15
- const newObj = { ...value };
16
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
17
- delete newObj[key];
18
- onChange(newObj);
19
- }, children: "Delete Key" }) })] }), _jsx(TypeInput, { type: type.type, value: item, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: newItem => {
20
- onChange(sortObjectKeysAlphabetically({ ...value, [key]: newItem }));
21
- } })] }, key))) })), _jsxs("div", { class: "add-item-container", children: [_jsxs(Select, { value: newKey, onInput: event => {
18
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
19
+ return (_jsxs("div", { class: "field field--container field--nestedentitymap", children: [existingKeys.length > 0 && (_jsx("ul", { children: Object.entries(value).map(([key, item]) => {
20
+ const name = instanceNamesByEntity[type.secondaryEntity]?.find(instance => instance.id === key)
21
+ ?.name ?? key;
22
+ return (_jsxs("li", { class: "container-item dict-item", children: [_jsxs("div", { className: "container-item-header", children: [_jsx("div", { className: "container-item-title", children: _jsxs("span", { children: [_jsx("strong", { children: name }), " ", _jsx("span", { className: "id", children: key })] }) }), _jsx("div", { className: "btns", children: _jsxs("button", { class: "destructive", onClick: () => {
23
+ const newObj = { ...value };
24
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
25
+ delete newObj[key];
26
+ onChange(newObj);
27
+ }, children: ["Delete ", name] }) })] }), _jsx(TypeInput, { type: type.type, path: path === undefined ? key : `${path}.${key}`, value: item, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: newItem => {
28
+ onChange(sortObjectKeysAlphabetically({ ...value, [key]: newItem }));
29
+ } })] }, key));
30
+ }) })), _jsxs("div", { class: "add-item-container", children: [_jsxs(Select, { value: newKey, onInput: event => {
22
31
  setNewKey(event.currentTarget.value);
23
- }, disabled: secondaryInstances.length === 0, children: [secondaryInstances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), secondaryInstances.map(instance => (_jsx("option", { value: instance.id, children: instance.name }, instance.id)))] }), _jsx("button", { onClick: () => {
32
+ }, disabled: secondaryInstances.length === 0, children: [secondaryInstances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), secondaryInstances.map(instance => (_jsx("option", { value: instance.id, children: instance.name }, instance.id)))] }), _jsxs("button", { onClick: () => {
24
33
  onChange(sortObjectKeysAlphabetically({
25
34
  ...value,
26
35
  [newKey]: createTypeSkeleton(getDeclFromDeclName, type.type),
27
36
  }));
28
37
  setNewKey("");
29
- }, disabled: newKey === "", children: "Add Key" })] })] }));
38
+ }, disabled: newKey === "", children: ["Add ", newKey === "" ? "Key" : toTitleCase(newKey)] })] })] }));
30
39
  };
@@ -4,7 +4,8 @@ import type { InstanceNamesByEntity } from "../../hooks/useInstanceNamesByEntity
4
4
  import type { GetDeclFromDeclName } from "../../hooks/useSecondaryDeclarations.ts";
5
5
  type Props = {
6
6
  type: SerializedObjectType;
7
- value: Record<string, unknown>;
7
+ path: string | undefined;
8
+ value: unknown;
8
9
  instanceNamesByEntity: InstanceNamesByEntity;
9
10
  getDeclFromDeclName: GetDeclFromDeclName;
10
11
  onChange: (value: Record<string, unknown>) => void;
@@ -5,20 +5,25 @@ import { validateObjectConstraints } from "../../../shared/validation/object.js"
5
5
  import { Markdown } from "../../utils/Markdown.js";
6
6
  import { createTypeSkeleton } from "../../utils/typeSkeleton.js";
7
7
  import { TypeInput } from "./TypeInput.js";
8
+ import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
8
9
  import { ValidationErrors } from "./utils/ValidationErrors.js";
9
- export const ObjectTypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
10
+ export const ObjectTypeInput = ({ type, path, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
11
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
12
+ return _jsx(MismatchingTypeError, { expected: "object", actual: value });
13
+ }
10
14
  const errors = validateObjectConstraints(type, Object.keys(type.properties), value);
11
- return (_jsxs("div", { class: "field field--container field--object", children: [_jsx("ul", { children: Object.entries(type.properties).map(([key, memberDecl]) => (_jsxs("li", { class: "container-item object-item", children: [_jsxs("div", { className: "container-item-header", children: [_jsxs("div", { className: "container-item-title", children: [_jsx("strong", { children: toTitleCase(key) }), memberDecl.comment === undefined ? null : (_jsx(Markdown, { class: "comment", string: memberDecl.comment }))] }), memberDecl.isRequired ? null : value[key] === undefined ? (_jsx("button", { onClick: () => {
15
+ return (_jsxs("div", { class: "field field--container field--object", children: [_jsx("ul", { children: Object.entries(type.properties).map(([key, memberDecl]) => (_jsxs("li", { class: "container-item object-item", children: [_jsxs("div", { className: "container-item-header", children: [_jsxs("div", { className: "container-item-title", children: [_jsx("strong", { children: toTitleCase(key) }), memberDecl.comment === undefined ? null : (_jsx(Markdown, { class: "comment", string: memberDecl.comment }))] }), memberDecl.isRequired ? null : value[key] ===
16
+ undefined ? (_jsxs("button", { onClick: () => {
12
17
  onChange(sortObjectKeys({
13
18
  ...value,
14
19
  [key]: createTypeSkeleton(getDeclFromDeclName, memberDecl.type),
15
20
  }, Object.keys(type.properties)));
16
- }, children: "Add Property" })) : (_jsx("button", { class: "destructive", onClick: () => {
21
+ }, children: ["Add ", toTitleCase(key)] })) : (_jsxs("button", { class: "destructive", onClick: () => {
17
22
  const newObj = { ...value };
18
23
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
19
24
  delete newObj[key];
20
25
  onChange(newObj);
21
- }, children: "Remove Property" }))] }), memberDecl.isRequired || value[key] !== undefined ? (_jsx(TypeInput, { type: memberDecl.type, value: value[key], instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: newItem => {
26
+ }, children: ["Remove ", toTitleCase(key)] }))] }), memberDecl.isRequired || value[key] !== undefined ? (_jsx(TypeInput, { type: memberDecl.type, path: path === undefined ? key : `${path}.${key}`, value: value[key], instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: newItem => {
22
27
  onChange(sortObjectKeys({ ...value, [key]: newItem }, Object.keys(type.properties)));
23
28
  } })) : null] }, key))) }), _jsx(ValidationErrors, { errors: errors })] }));
24
29
  };
@@ -3,7 +3,7 @@ import type { SerializedReferenceIdentifierType } from "../../../node/schema/typ
3
3
  import type { InstanceNamesByEntity } from "../../hooks/useInstanceNamesByEntity.ts";
4
4
  type Props = {
5
5
  type: SerializedReferenceIdentifierType;
6
- value: string;
6
+ value: unknown;
7
7
  instanceNamesByEntity: InstanceNamesByEntity;
8
8
  onChange: (value: string) => void;
9
9
  };
@@ -1,10 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { Select } from "../Select.js";
3
+ import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
3
4
  import { ValidationErrors } from "./utils/ValidationErrors.js";
4
5
  export const ReferenceIdentifierTypeInput = ({ type, value, instanceNamesByEntity, onChange, }) => {
6
+ if (typeof value !== "string") {
7
+ return _jsx(MismatchingTypeError, { expected: "string identifier", actual: value });
8
+ }
5
9
  const instances = (instanceNamesByEntity[type.entity] ?? [])
6
10
  .slice()
7
- .sort((a, b) => a.name.localeCompare(b.name));
11
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
8
12
  return (_jsxs("div", { class: "field", children: [_jsxs(Select, { value: value, onInput: event => {
9
13
  onChange(event.currentTarget.value);
10
14
  }, disabled: instances.length === 0, "aria-invalid": !value, children: [instances.length === 0 ? (_jsx("option", { value: "", disabled: true, children: "No instances available" })) : (_jsx("option", { value: "", disabled: true, children: "No selected instance" })), instances.map(instance => (_jsx("option", { value: instance.id, children: instance.name }, instance.id)))] }), _jsx(ValidationErrors, { errors: !value ? [ReferenceError("no reference provided")] : [] })] }));
@@ -2,7 +2,7 @@ import type { FunctionComponent } from "preact";
2
2
  import type { SerializedStringType } from "../../../node/schema/types/primitives/StringType.ts";
3
3
  type Props = {
4
4
  type: SerializedStringType;
5
- value: string;
5
+ value: unknown;
6
6
  onChange: (value: string) => void;
7
7
  };
8
8
  export declare const StringTypeInput: FunctionComponent<Props>;
@@ -1,13 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
2
2
  import { validateStringConstraints } from "../../../shared/validation/string.js";
3
3
  import { Markdown } from "../../utils/Markdown.js";
4
+ import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
4
5
  import { ValidationErrors } from "./utils/ValidationErrors.js";
5
6
  export const StringTypeInput = ({ type, value, onChange }) => {
7
+ if (typeof value !== "string") {
8
+ return _jsx(MismatchingTypeError, { expected: "string", actual: value });
9
+ }
6
10
  const { minLength, maxLength, pattern, isMarkdown } = type;
7
11
  const errors = validateStringConstraints(type, value);
8
- return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "editor", children: [_jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
9
- onChange(event.currentTarget.value);
10
- }, "aria-invalid": errors.length > 0 }), _jsx(ValidationErrors, { errors: errors })] }), _jsx("div", { className: "preview", children: _jsx(Markdown, { string: value }) })] })) : (_jsxs("div", { className: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern, onInput: event => {
12
+ return (_jsx("div", { class: "field field--string", children: isMarkdown ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "editor", children: [_jsx("div", { class: "textarea-grow-wrap", "data-value": value, children: _jsx("textarea", { value: value, minLength: minLength, maxLength: maxLength, onInput: event => {
13
+ onChange(event.currentTarget.value);
14
+ }, "aria-invalid": errors.length > 0 }) }), _jsx(ValidationErrors, { errors: errors })] }), _jsx("div", { className: "preview", children: _jsx(Markdown, { string: value }) })] })) : (_jsxs("div", { className: "editor", children: [_jsx("input", { type: "text", value: value, minLength: minLength, maxLength: maxLength, pattern: pattern === undefined
15
+ ? undefined
16
+ : pattern.startsWith("^(?:") && pattern.endsWith(")$")
17
+ ? pattern.slice(4, -2)
18
+ : `.*${pattern}.*`, onInput: event => {
11
19
  onChange(event.currentTarget.value);
12
20
  }, "aria-invalid": errors.length > 0 }), _jsx(ValidationErrors, { errors: errors })] })) }));
13
21
  };
@@ -4,10 +4,11 @@ import type { InstanceNamesByEntity } from "../../hooks/useInstanceNamesByEntity
4
4
  import type { GetDeclFromDeclName } from "../../hooks/useSecondaryDeclarations.ts";
5
5
  type Props = {
6
6
  type: SerializedType;
7
+ path: string | undefined;
7
8
  value: unknown;
8
9
  instanceNamesByEntity: InstanceNamesByEntity;
9
10
  getDeclFromDeclName: GetDeclFromDeclName;
10
11
  onChange: (value: unknown) => void;
11
12
  };
12
- export declare const TypeInput: FunctionComponent<Props>;
13
- export {};
13
+ declare const MemoizedTypeInput: FunctionComponent<Props>;
14
+ export { MemoizedTypeInput as TypeInput };
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { memo } from "preact/compat";
2
3
  import { assertExhaustive } from "../../../shared/utils/typeSafety.js";
3
4
  import { ArrayTypeInput } from "./ArrayTypeInput.js";
4
5
  import { BooleanTypeInput } from "./BooleanTypeInput.js";
@@ -12,79 +13,36 @@ import { NestedEntityMapTypeInput } from "./NestedEntityMapTypeInput.js";
12
13
  import { ObjectTypeInput } from "./ObjectTypeInput.js";
13
14
  import { ReferenceIdentifierTypeInput } from "./ReferenceIdentifierTypeInput.js";
14
15
  import { StringTypeInput } from "./StringTypeInput.js";
15
- import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
16
- export const TypeInput = ({ type, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
16
+ const TypeInput = ({ type, path, value, instanceNamesByEntity, getDeclFromDeclName, onChange, }) => {
17
+ // console.log("rendering node at path ", path ?? "<root>")
17
18
  switch (type.kind) {
18
19
  case "BooleanType":
19
- if (typeof value === "boolean") {
20
- return _jsx(BooleanTypeInput, { type: type, value: value, onChange: onChange });
21
- }
22
- else {
23
- return _jsx(MismatchingTypeError, { expected: "boolean", actual: value });
24
- }
20
+ return _jsx(BooleanTypeInput, { type: type, value: value, onChange: onChange });
25
21
  case "DateType":
26
- if (typeof value === "string") {
27
- return _jsx(DateTypeInput, { type: type, value: value, onChange: onChange });
28
- }
29
- else {
30
- return _jsx(MismatchingTypeError, { expected: "date string", actual: value });
31
- }
22
+ return _jsx(DateTypeInput, { type: type, value: value, onChange: onChange });
32
23
  case "FloatType":
33
- if (typeof value === "number") {
34
- return _jsx(FloatTypeInput, { type: type, value: value, onChange: onChange });
35
- }
36
- else {
37
- return _jsx(MismatchingTypeError, { expected: "float", actual: value });
38
- }
24
+ return _jsx(FloatTypeInput, { type: type, value: value, onChange: onChange });
39
25
  case "IntegerType":
40
- if (typeof value === "number" && Number.isInteger(value)) {
41
- return _jsx(IntegerTypeInput, { type: type, value: value, onChange: onChange });
42
- }
43
- else {
44
- return _jsx(MismatchingTypeError, { expected: "integer", actual: value });
45
- }
26
+ return _jsx(IntegerTypeInput, { type: type, value: value, onChange: onChange });
46
27
  case "StringType":
47
- if (typeof value === "string") {
48
- return _jsx(StringTypeInput, { type: type, value: value, onChange: onChange });
49
- }
50
- else {
51
- return _jsx(MismatchingTypeError, { expected: "string", actual: value });
52
- }
28
+ return _jsx(StringTypeInput, { type: type, value: value, onChange: onChange });
53
29
  case "ArrayType":
54
- if (Array.isArray(value)) {
55
- return (_jsx(ArrayTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
56
- }
57
- else {
58
- return _jsx(MismatchingTypeError, { expected: "array", actual: value });
59
- }
30
+ return (_jsx(ArrayTypeInput, { type: type, path: path, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
60
31
  case "ObjectType":
61
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
62
- return (_jsx(ObjectTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
63
- }
64
- else {
65
- return _jsx(MismatchingTypeError, { expected: "object", actual: value });
66
- }
32
+ return (_jsx(ObjectTypeInput, { type: type, path: path, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
67
33
  case "TypeArgumentType":
68
34
  return _jsx(TypeArgumentTypeInput, { type: type });
69
35
  case "ReferenceIdentifierType":
70
- if (typeof value === "string") {
71
- return (_jsx(ReferenceIdentifierTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, onChange: onChange }));
72
- }
73
- else {
74
- return _jsx(MismatchingTypeError, { expected: "string identifier", actual: value });
75
- }
36
+ return (_jsx(ReferenceIdentifierTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, onChange: onChange }));
76
37
  case "IncludeIdentifierType":
77
- return (_jsx(IncludeIdentifierTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
38
+ return (_jsx(IncludeIdentifierTypeInput, { type: type, path: path, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
78
39
  case "NestedEntityMapType":
79
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
80
- return (_jsx(NestedEntityMapTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
81
- }
82
- else {
83
- return _jsx(MismatchingTypeError, { expected: "entity map", actual: value });
84
- }
40
+ return (_jsx(NestedEntityMapTypeInput, { type: type, path: path, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
85
41
  case "EnumType":
86
- return (_jsx(EnumTypeInput, { type: type, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
42
+ return (_jsx(EnumTypeInput, { type: type, path: path, value: value, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: onChange }));
87
43
  default:
88
44
  return assertExhaustive(type);
89
45
  }
90
46
  };
47
+ const MemoizedTypeInput = memo(TypeInput, (prevProps, nextProps) => prevProps.value === nextProps.value && prevProps.onChange === nextProps.onChange);
48
+ export { MemoizedTypeInput as TypeInput };
@@ -1,3 +1,3 @@
1
1
  import type { GetAllInstancesResponseBody } from "../../shared/api.ts";
2
2
  export type InstanceNamesByEntity = GetAllInstancesResponseBody["instances"];
3
- export declare const useInstanceNamesByEntity: (locales?: string[]) => [InstanceNamesByEntity, () => void];
3
+ export declare const useInstanceNamesByEntity: (locales?: string[]) => [InstanceNamesByEntity | undefined, () => void];
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "preact/hooks";
2
2
  import { getAllInstances } from "../api.js";
3
3
  export const useInstanceNamesByEntity = (locales = []) => {
4
- const [instanceNamesByEntity, setInstanceNamesByEntity] = useState({});
4
+ const [instanceNamesByEntity, setInstanceNamesByEntity] = useState();
5
5
  const updateInstanceNamesByEntity = useCallback(() => {
6
6
  getAllInstances(locales)
7
7
  .then(data => {
@@ -1,3 +1,3 @@
1
1
  import type { SerializedSecondaryDecl } from "../../node/schema/declarations/Declaration.ts";
2
2
  export type GetDeclFromDeclName = (name: string) => SerializedSecondaryDecl | undefined;
3
- export declare const useGetDeclFromDeclName: () => GetDeclFromDeclName;
3
+ export declare const useGetDeclFromDeclName: () => [GetDeclFromDeclName, loaded: boolean];
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from "preact/hooks";
2
2
  import { getAllDeclarations } from "../api.js";
3
3
  export const useGetDeclFromDeclName = () => {
4
- const [secondaryDeclarations, setSecondaryDeclarations] = useState([]);
4
+ const [secondaryDeclarations, setSecondaryDeclarations] = useState();
5
5
  useEffect(() => {
6
6
  getAllDeclarations()
7
7
  .then(data => {
@@ -15,6 +15,6 @@ export const useGetDeclFromDeclName = () => {
15
15
  }
16
16
  });
17
17
  }, []);
18
- const getDeclFromDeclName = useCallback((name) => secondaryDeclarations.find(decl => decl.name === name), [secondaryDeclarations]);
19
- return getDeclFromDeclName;
18
+ const getDeclFromDeclName = useCallback((name) => secondaryDeclarations?.find(decl => decl.name === name), [secondaryDeclarations]);
19
+ return [getDeclFromDeclName, secondaryDeclarations !== undefined];
20
20
  };
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useLocation, useRoute } from "preact-iso";
3
3
  import { useEffect, useState } from "preact/hooks";
4
- import { getDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
4
+ import { getSerializedDisplayNameFromEntityInstance } from "../../shared/utils/displayName.js";
5
5
  import { toTitleCase } from "../../shared/utils/string.js";
6
6
  import { validateLocaleIdentifier } from "../../shared/validation/identifier.js";
7
7
  import { createInstanceByEntityNameAndId } from "../api.js";
@@ -15,7 +15,7 @@ import { createTypeSkeleton } from "../utils/typeSkeleton.js";
15
15
  import { NotFound } from "./NotFound.js";
16
16
  export const CreateInstance = () => {
17
17
  const { params: { name }, } = useRoute();
18
- const getDeclFromDeclName = useGetDeclFromDeclName();
18
+ const [getDeclFromDeclName, declsLoaded] = useGetDeclFromDeclName();
19
19
  const entityFromRoute = useEntityFromRoute();
20
20
  const [instanceNamesByEntity] = useInstanceNamesByEntity();
21
21
  const [instance, setInstance] = useState();
@@ -26,10 +26,15 @@ export const CreateInstance = () => {
26
26
  }
27
27
  }, [getDeclFromDeclName, entityFromRoute]);
28
28
  const { route } = useLocation();
29
+ useEffect(() => {
30
+ const entityName = entityFromRoute?.entity.name ?? name;
31
+ document.title =
32
+ entityName === undefined ? "Not found" : "New " + toTitleCase(entityName) + " — TSONDB";
33
+ }, [entityFromRoute?.entity.name, name]);
29
34
  if (!name) {
30
35
  return _jsx(NotFound, {});
31
36
  }
32
- if (!entityFromRoute) {
37
+ if (!entityFromRoute || !instanceNamesByEntity || !declsLoaded) {
33
38
  return (_jsxs("div", { children: [_jsx("h1", { children: name }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
34
39
  }
35
40
  const { entity, isLocaleEntity } = entityFromRoute;
@@ -39,13 +44,22 @@ export const CreateInstance = () => {
39
44
  if (name) {
40
45
  createInstanceByEntityNameAndId(entity.name, instance, isLocaleEntity ? customId : undefined)
41
46
  .then(createdInstance => {
42
- if (name === "saveandaddanother") {
43
- setInstance(createTypeSkeleton(getDeclFromDeclName, entity.type));
44
- setCustomId("");
45
- alert(`Instance of entity ${entity.name} created successfully with identifier ${createdInstance.instance.id}. You can add another instance now.`);
46
- }
47
- else {
48
- route(`/entities/${entity.name}?created=${encodeURIComponent(createdInstance.instance.id)}`);
47
+ switch (name) {
48
+ case "saveandcontinue": {
49
+ route(`/entities/${entity.name}/instances/${createdInstance.instance.id}`);
50
+ break;
51
+ }
52
+ case "saveandaddanother": {
53
+ setInstance(createTypeSkeleton(getDeclFromDeclName, entity.type));
54
+ setCustomId("");
55
+ alert(`Instance of entity ${entity.name} created successfully with identifier ${createdInstance.instance.id}. You can add another instance now.`);
56
+ break;
57
+ }
58
+ case "save":
59
+ default: {
60
+ route(`/entities/${entity.name}?created=${encodeURIComponent(createdInstance.instance.id)}`);
61
+ break;
62
+ }
49
63
  }
50
64
  })
51
65
  .catch((error) => {
@@ -56,15 +70,15 @@ export const CreateInstance = () => {
56
70
  }
57
71
  };
58
72
  const defaultName = customId || `New ${toTitleCase(entity.name)}`;
59
- const instanceName = getDisplayNameFromEntityInstance(entity, instance, defaultName);
73
+ const instanceName = getSerializedDisplayNameFromEntityInstance(entity, instance, defaultName);
60
74
  const idErrors = isLocaleEntity ? validateLocaleIdentifier(customId) : [];
61
75
  return (_jsxs(Layout, { breadcrumbs: [
62
76
  { url: "/", label: "Home" },
63
77
  { url: `/entities/${entity.name}`, label: entity.name },
64
78
  ], children: [_jsx("div", { class: "header-with-btns", children: _jsx("h1", { class: instanceName.length === 0 ? "empty-name" : undefined, children: instanceName || defaultName }) }), isLocaleEntity && (_jsxs("div", { class: "field field--id", children: [_jsx("label", { htmlFor: "id", children: "ID" }), _jsx("p", { className: "comment", children: "The instance\u2019s identifier. An IETF language tag (BCP47)." }), _jsx("input", { type: "text", id: "id", value: customId, required: true, pattern: "[a-z]{2,3}(-[A-Z]{2,3})?", placeholder: "en-US, de-DE, \u2026", onInput: event => {
65
79
  setCustomId(event.currentTarget.value);
66
- }, "aria-invalid": idErrors.length > 0 }), _jsx(ValidationErrors, { errors: idErrors })] })), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, value: instance, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: value => {
80
+ }, "aria-invalid": idErrors.length > 0 }), _jsx(ValidationErrors, { errors: idErrors })] })), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TypeInput, { type: entity.type, path: undefined, value: instance, instanceNamesByEntity: instanceNamesByEntity, getDeclFromDeclName: getDeclFromDeclName, onChange: value => {
67
81
  console.log("onChange", value);
68
82
  setInstance(value);
69
- } }), _jsxs("div", { className: "btns", children: [_jsx("button", { type: "submit", class: "primary", name: "save", children: "Save" }), _jsx("button", { type: "submit", class: "primary", name: "saveandaddanother", children: "Save and Add Another" })] })] })] }));
83
+ } }), _jsxs("div", { class: "form-footer btns", children: [_jsx("button", { type: "submit", class: "primary", name: "save", children: "Save" }), _jsx("button", { type: "submit", class: "primary", name: "saveandcontinue", children: "Save and Continue" }), _jsx("button", { type: "submit", class: "primary", name: "saveandaddanother", children: "Save and Add Another" })] })] })] }));
70
84
  };
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useRoute } from "preact-iso";
3
- import { useEffect } from "preact/hooks";
3
+ import { useEffect, useState } from "preact/hooks";
4
4
  import { getGitStatusForDisplay, getLabelForGitStatus } from "../../shared/utils/git.js";
5
+ import { toTitleCase } from "../../shared/utils/string.js";
5
6
  import { deleteInstanceByEntityNameAndId, getEntityByName, getInstancesByEntityName, } from "../api.js";
6
7
  import { Layout } from "../components/Layout.js";
7
8
  import { useAPIResource } from "../hooks/useAPIResource.js";
@@ -11,8 +12,12 @@ import { NotFound } from "./NotFound.js";
11
12
  const mapInstances = (data) => data.instances;
12
13
  export const Entity = () => {
13
14
  const { params: { name }, query: { created }, } = useRoute();
15
+ const [searchText, setSearchText] = useState("");
14
16
  const [entity] = useAPIResource(getEntityByName, name ?? "");
15
17
  const [instances, reloadInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, name ?? "");
18
+ useEffect(() => {
19
+ document.title = toTitleCase(entity?.declaration.namePlural ?? name ?? "") + " — TSONDB";
20
+ }, [entity?.declaration.namePlural, name]);
16
21
  useEffect(() => {
17
22
  if (created) {
18
23
  const instanceElement = document.getElementById(`instance-${created}`);
@@ -25,20 +30,29 @@ export const Entity = () => {
25
30
  return _jsx(NotFound, {});
26
31
  }
27
32
  if (!entity || !instances) {
28
- return (_jsxs("div", { children: [_jsx("h1", { children: name }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
33
+ return (_jsxs("div", { children: [_jsx("h1", { children: toTitleCase(entity?.declaration.namePlural ?? name) }), _jsx("p", { className: "loading", children: "Loading \u2026" })] }));
29
34
  }
30
- return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsxs("div", { class: "header-with-btns", children: [_jsx("h1", { children: name }), _jsx("a", { class: "btn btn--primary", href: `/entities/${entity.declaration.name}/instances/create`, children: "Add" })] }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment })), _jsxs("p", { children: [instances.length, " instance", instances.length === 1 ? "" : "s"] }), _jsx("ul", { class: "instances", children: instances.map(instance => {
35
+ const lowerSearchText = searchText.toLowerCase();
36
+ const filteredInstances = searchText.length === 0
37
+ ? instances
38
+ : instances.filter(instance => instance.id.includes(searchText) ||
39
+ instance.displayName.toLowerCase().includes(lowerSearchText));
40
+ return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsxs("div", { class: "header-with-btns", children: [_jsx("h1", { children: toTitleCase(entity.declaration.namePlural) }), _jsx("a", { class: "btn btn--primary", href: `/entities/${entity.declaration.name}/instances/create`, children: "Add" })] }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment })), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${filteredInstances.length.toString()} of `, instances.length, " instance", instances.length === 1 ? "" : "s"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
41
+ e.preventDefault();
42
+ }, children: [_jsx("label", { htmlFor: "instance-search", class: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: "instance-search", value: searchText, onInput: event => {
43
+ setSearchText(event.currentTarget.value);
44
+ } })] })] }), _jsx("ul", { class: "entries entries--instances", children: filteredInstances.map(instance => {
31
45
  const gitStatusForDisplay = getGitStatusForDisplay(instance.gitStatus);
32
- return (_jsxs("li", { id: `instance-${instance.id}`, class: `instance-item ${created === instance.id ? "instance-item--created" : ""} ${gitStatusForDisplay === undefined ? "" : `git-status--${gitStatusForDisplay}`}`, children: [_jsx("h2", { children: instance.displayName }), _jsx("p", { "aria-hidden": true, class: "id", children: instance.id }), gitStatusForDisplay !== undefined && (_jsx("p", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay })), _jsxs("div", { className: "btns", children: [_jsx("a", { href: `/entities/${entity.declaration.name}/instances/${instance.id}`, class: "btn", children: "Edit" }), _jsx("button", { class: "destructive", onClick: () => {
33
- if (confirm("Are you sure you want to delete this instance?")) {
34
- deleteInstanceByEntityNameAndId(entity.declaration.name, instance.id)
35
- .then(() => reloadInstances())
36
- .catch((error) => {
37
- if (error instanceof Error) {
38
- alert("Error deleting instance:\n\n" + error.toString());
46
+ return (_jsxs("li", { id: `instance-${instance.id}`, class: `entries-item ${created === instance.id ? "entries-item--created" : ""} ${gitStatusForDisplay === undefined ? "" : `git-status--${gitStatusForDisplay}`}`, children: [_jsx("h2", { class: "entries-item__title", children: instance.displayName }), _jsx("p", { "aria-hidden": true, class: "entries-item__subtitle entries-item__subtitle--id", children: instance.id }), _jsxs("div", { class: "entries-item__side", children: [gitStatusForDisplay !== undefined && (_jsx("p", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay })), _jsxs("div", { class: "btns", children: [_jsx("a", { href: `/entities/${entity.declaration.name}/instances/${instance.id}`, class: "btn", children: "Edit" }), _jsx("button", { class: "destructive", onClick: () => {
47
+ if (confirm("Are you sure you want to delete this instance?")) {
48
+ deleteInstanceByEntityNameAndId(entity.declaration.name, instance.id)
49
+ .then(() => reloadInstances())
50
+ .catch((error) => {
51
+ if (error instanceof Error) {
52
+ alert("Error deleting instance:\n\n" + error.toString());
53
+ }
54
+ });
39
55
  }
40
- });
41
- }
42
- }, children: "Delete" })] })] }, instance.id));
56
+ }, children: "Delete" })] })] })] }, instance.id));
43
57
  }) })] }));
44
58
  };
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ import { useEffect, useState } from "preact/hooks";
2
3
  import { toTitleCase } from "../../shared/utils/string.js";
3
4
  import { getAllEntities } from "../api.js";
4
5
  import { Layout } from "../components/Layout.js";
@@ -7,5 +8,18 @@ import { Markdown } from "../utils/Markdown.js";
7
8
  const mapEntities = (data) => data.declarations.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name));
8
9
  export const Home = () => {
9
10
  const [entities] = useMappedAPIResource(getAllEntities, mapEntities);
10
- return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "Entities" }), _jsx("ul", { class: "entities", children: (entities ?? []).map(entity => (_jsxs("li", { class: "entity-item", children: [_jsxs("div", { className: "title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.name) }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment }))] }), _jsxs("p", { class: "meta", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { className: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) })] }, entity.declaration.name))) })] }));
11
+ useEffect(() => {
12
+ document.title = "Entities — TSONDB";
13
+ }, []);
14
+ const [searchText, setSearchText] = useState("");
15
+ const lowerSearchText = searchText.toLowerCase().replaceAll(" ", "");
16
+ const filteredEntities = searchText.length === 0
17
+ ? entities
18
+ : entities?.filter(entity => entity.declaration.name.toLowerCase().includes(lowerSearchText) ||
19
+ entity.declaration.namePlural.toLowerCase().includes(lowerSearchText));
20
+ return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: "Home" }], children: [_jsx("h1", { children: "Entities" }), _jsxs("div", { className: "list-header", children: [_jsxs("p", { class: "instance-count", children: [searchText === "" ? "" : `${(filteredEntities?.length ?? 0).toString()} of `, entities?.length ?? 0, " entit", entities?.length === 1 ? "y" : "ies"] }), _jsxs("form", { action: "", rel: "search", onSubmit: e => {
21
+ e.preventDefault();
22
+ }, children: [_jsx("label", { htmlFor: "entity-search", class: "visually-hidden", children: "Search" }), _jsx("input", { type: "text", id: "entity-search", value: searchText, onInput: event => {
23
+ setSearchText(event.currentTarget.value);
24
+ } })] })] }), _jsx("ul", { class: "entries entries--entities", children: (filteredEntities ?? []).map(entity => (_jsxs("li", { class: "entries-item", children: [_jsxs("div", { class: "entries-item__title", children: [_jsx("h2", { children: toTitleCase(entity.declaration.namePlural) }), entity.declaration.comment && (_jsx(Markdown, { class: "description", string: entity.declaration.comment }))] }), _jsxs("p", { class: "entries-item__subtitle", children: [entity.instanceCount, " instance", entity.instanceCount === 1 ? "" : "s"] }), _jsx("div", { class: "entries-item__side", children: _jsx("div", { class: "btns", children: _jsx("a", { href: `/entities/${entity.declaration.name}`, class: "btn", children: "View" }) }) })] }, entity.declaration.name))) })] }));
11
25
  };