tsondb 0.12.5 → 0.12.7
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.
- package/dist/src/node/schema/declarations/EntityDecl.d.ts +9 -7
- package/dist/src/node/schema/declarations/EntityDecl.js +2 -2
- package/dist/src/node/schema/declarations/EnumDecl.d.ts +2 -0
- package/dist/src/node/schema/declarations/EnumDecl.js +2 -0
- package/dist/src/node/schema/types/Type.d.ts +20 -6
- package/dist/src/node/schema/types/primitives/FloatType.d.ts +5 -2
- package/dist/src/node/schema/types/primitives/FloatType.js +10 -4
- package/dist/src/node/server/api/git.js +4 -0
- package/dist/src/node/server/api/index.js +2 -0
- package/dist/src/node/server/api/search.d.ts +1 -0
- package/dist/src/node/server/api/search.js +50 -0
- package/dist/src/node/server/utils/instanceOperations.js +3 -3
- package/dist/src/node/server/utils/query.d.ts +2 -0
- package/dist/src/node/server/utils/query.js +7 -0
- package/dist/src/node/utils/childInstances.d.ts +1 -1
- package/dist/src/node/utils/childInstances.js +15 -9
- package/dist/src/node/utils/displayName.js +1 -0
- package/dist/src/shared/api.d.ts +4 -0
- package/dist/src/shared/schema/declarations/EntityDecl.d.ts +13 -9
- package/dist/src/shared/schema/declarations/EnumDecl.d.ts +8 -0
- package/dist/src/shared/schema/declarations/EnumDecl.js +5 -0
- package/dist/src/shared/schema/types/FloatType.d.ts +6 -5
- package/dist/src/shared/schema/types/FloatType.js +1 -0
- package/dist/src/shared/utils/object.d.ts +1 -1
- package/dist/src/web/api/search.d.ts +2 -0
- package/dist/src/web/api/search.js +7 -0
- package/dist/src/web/components/Layout.js +1 -1
- package/dist/src/web/components/git/GitStatusIndicator.js +1 -1
- package/dist/src/web/components/typeInputs/FloatTypeInput.d.ts +1 -1
- package/dist/src/web/components/typeInputs/FloatTypeInput.js +2 -1
- package/dist/src/web/index.js +2 -1
- package/dist/src/web/routes/Search.d.ts +2 -0
- package/dist/src/web/routes/Search.js +56 -0
- package/package.json +1 -1
- package/public/css/styles.css +10 -0
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
import { Lazy } from "../../../shared/utils/lazy.ts";
|
|
2
|
-
import type { Leaves } from "../../../shared/utils/object.ts";
|
|
3
2
|
import type { DisplayNameCustomizer } from "../../utils/displayName.ts";
|
|
4
|
-
import type { GetNestedDeclarations, GetReferences, Predicate,
|
|
3
|
+
import type { GetNestedDeclarations, GetReferences, Predicate, Serialized, TypeArgumentsResolver, Validator } from "../Node.ts";
|
|
5
4
|
import { NodeKind } from "../Node.ts";
|
|
6
5
|
import type { MemberDecl, ObjectType } from "../types/generic/ObjectType.ts";
|
|
7
6
|
import { StringType } from "../types/primitives/StringType.ts";
|
|
8
|
-
import type {
|
|
7
|
+
import type { NestedEntityMapType } from "../types/references/NestedEntityMapType.ts";
|
|
9
8
|
import type { BaseDecl } from "./Declaration.ts";
|
|
10
9
|
import { TypeAliasDecl } from "./TypeAliasDecl.ts";
|
|
11
10
|
export type GenericEntityDisplayName = string | {
|
|
12
11
|
pathToLocaleMap?: string;
|
|
13
12
|
pathInLocaleMap?: string;
|
|
14
13
|
} | null;
|
|
15
|
-
export type EntityDisplayName<T extends TConstraint> =
|
|
14
|
+
export type EntityDisplayName<T extends TConstraint> = PathTo<T, StringType> | {
|
|
16
15
|
/**
|
|
17
16
|
* @default "translations"
|
|
18
17
|
*/
|
|
19
|
-
pathToLocaleMap?:
|
|
18
|
+
pathToLocaleMap?: PathTo<T, NestedEntityMapType>;
|
|
20
19
|
/**
|
|
21
20
|
* @default "name"
|
|
22
21
|
*/
|
|
23
22
|
pathInLocaleMap?: string;
|
|
24
23
|
} | null;
|
|
25
24
|
type TConstraint = Record<string, MemberDecl>;
|
|
25
|
+
type PathTo<T extends TConstraint, R> = {
|
|
26
|
+
[K in keyof T]: T[K] extends MemberDecl<infer V> ? V extends R ? K : R extends V ? string : T[K] extends ObjectType<infer P> ? `${Extract<K, string>}.${PathTo<P, R>}` : never : never;
|
|
27
|
+
}[Extract<keyof T, string>];
|
|
26
28
|
export interface EntityDecl<Name extends string = string, T extends TConstraint = TConstraint, FK extends Extract<keyof T, string> | undefined = Extract<keyof T, string> | undefined> extends BaseDecl<Name, []> {
|
|
27
29
|
kind: NodeKind["EntityDecl"];
|
|
28
30
|
namePlural: string;
|
|
@@ -31,7 +33,7 @@ export interface EntityDecl<Name extends string = string, T extends TConstraint
|
|
|
31
33
|
/**
|
|
32
34
|
* @default "name"
|
|
33
35
|
*/
|
|
34
|
-
displayName?:
|
|
36
|
+
displayName?: GenericEntityDisplayName;
|
|
35
37
|
displayNameCustomizer?: DisplayNameCustomizer<ObjectType<T>>;
|
|
36
38
|
isDeprecated?: boolean;
|
|
37
39
|
}
|
|
@@ -75,5 +77,5 @@ export declare const addEphemeralUUIDToType: <T extends TConstraint>(decl: Entit
|
|
|
75
77
|
}>;
|
|
76
78
|
export declare const createEntityIdentifierType: () => StringType;
|
|
77
79
|
export declare const createEntityIdentifierTypeAsDecl: <Name extends string>(decl: EntityDecl<Name>) => TypeAliasDecl<`${Name}_ID`, StringType, []>;
|
|
78
|
-
export declare const serializeEntityDecl:
|
|
80
|
+
export declare const serializeEntityDecl: <Name extends string, T extends TConstraint, FK extends Extract<keyof T, string> | undefined>(type: EntityDecl<Name, T, FK>) => Serialized<EntityDecl<Name, T, FK>>;
|
|
79
81
|
export declare const getReferencesForEntityDecl: GetReferences<EntityDecl>;
|
|
@@ -49,12 +49,12 @@ export const createEntityIdentifierTypeAsDecl = (decl) => TypeAliasDecl(decl.sou
|
|
|
49
49
|
name: (decl.name + "_ID"),
|
|
50
50
|
type: createEntityIdentifierType,
|
|
51
51
|
});
|
|
52
|
-
export const serializeEntityDecl = (type) => ({
|
|
52
|
+
export const serializeEntityDecl = ((type) => ({
|
|
53
53
|
...type,
|
|
54
54
|
type: serializeObjectType(type.type.value),
|
|
55
55
|
displayName: typeof type.displayName === "function"
|
|
56
56
|
? null
|
|
57
57
|
: type.displayName,
|
|
58
58
|
displayNameCustomizer: type.displayNameCustomizer !== undefined,
|
|
59
|
-
});
|
|
59
|
+
}));
|
|
60
60
|
export const getReferencesForEntityDecl = (decl, value, inDecl) => getReferencesForObjectType(decl.type.value, value, [...inDecl, decl]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type EnumValue } from "../../../shared/schema/declarations/EnumDecl.ts";
|
|
1
2
|
import { Lazy } from "../../../shared/utils/lazy.ts";
|
|
2
3
|
import type { GetNestedDeclarations, GetReferences, Predicate, Serializer, TypeArgumentsResolver, ValidatorOfParamDecl } from "../Node.ts";
|
|
3
4
|
import { NodeKind } from "../Node.ts";
|
|
@@ -33,3 +34,4 @@ export declare const resolveTypeArgumentsInEnumDecl: TypeArgumentsResolver<EnumD
|
|
|
33
34
|
export declare const serializeEnumDecl: Serializer<EnumDecl>;
|
|
34
35
|
export declare const getReferencesForEnumDecl: GetReferences<EnumDecl>;
|
|
35
36
|
export declare const cases: <T extends TConstraint>(decl: EnumDecl<string, T>) => EnumCaseDecl<T[keyof T]["type"]>[];
|
|
37
|
+
export declare const getAnyEnumCaseValue: <K extends string, V>(enumValue: { [Key in K]: EnumValue<Key, V>; }[K]) => V;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ENUM_DISCRIMINATOR_KEY, } from "../../../shared/schema/declarations/EnumDecl.js";
|
|
1
2
|
import { Lazy } from "../../../shared/utils/lazy.js";
|
|
2
3
|
import { onlyKeys } from "../../../shared/utils/object.js";
|
|
3
4
|
import { NodeKind } from "../Node.js";
|
|
@@ -46,3 +47,4 @@ export const serializeEnumDecl = decl => ({
|
|
|
46
47
|
});
|
|
47
48
|
export const getReferencesForEnumDecl = (decl, value, inDecl) => getReferencesForEnumType(decl.type.value, value, [...inDecl, decl]);
|
|
48
49
|
export const cases = (decl) => Object.values(decl.type.value.values);
|
|
50
|
+
export const getAnyEnumCaseValue = (enumValue) => enumValue[enumValue[ENUM_DISCRIMINATOR_KEY]];
|
|
@@ -25,14 +25,28 @@ type EnumCaseTypeAsType<Case extends string, T extends Type | null> = T extends
|
|
|
25
25
|
} : {
|
|
26
26
|
kind: Case;
|
|
27
27
|
};
|
|
28
|
-
|
|
28
|
+
type EnumCaseTypeAsDeepType<Case extends string, T extends Type | null> = T extends object ? {
|
|
29
|
+
kind: Case;
|
|
30
|
+
} & {
|
|
31
|
+
[AV in Case]: AsDeepType<T>;
|
|
32
|
+
} : {
|
|
33
|
+
kind: Case;
|
|
34
|
+
};
|
|
35
|
+
type MemberDeclsAsType<P extends Record<string, MemberDecl>> = {
|
|
29
36
|
[K in keyof P]: P[K] extends MemberDecl<Type, true> ? AsType<P[K]["type"]> : AsType<P[K]["type"]> | undefined;
|
|
30
|
-
}
|
|
31
|
-
|
|
37
|
+
};
|
|
38
|
+
type MemberDeclsAsDeepType<P extends Record<string, MemberDecl>> = {
|
|
39
|
+
[K in keyof P]: P[K] extends MemberDecl<Type, true> ? AsDeepType<P[K]["type"]> : // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- it does make a difference here
|
|
40
|
+
AsDeepType<P[K]["type"]> | undefined;
|
|
41
|
+
};
|
|
42
|
+
export type AsDeepType<T extends Type> = T extends ArrayType<infer I> ? AsDeepType<I>[] : T extends ObjectType<infer P> ? MemberDeclsAsDeepType<P> : T extends BooleanType ? boolean : T extends DateType ? Date : T extends FloatType ? number : T extends IntegerType ? number : T extends StringType ? string : T extends TypeArgumentType ? unknown : T extends IncludeIdentifierType<TypeParameter[], infer Decl> ? Decl extends TypeAliasDecl<string, infer TA> ? AsDeepType<TA> : Decl extends EnumDecl<string, infer EC> ? AsDeepType<EnumType<EC>> : unknown : T extends NestedEntityMapType<string, infer TC> ? {
|
|
43
|
+
[id: string]: MemberDeclsAsDeepType<TC>;
|
|
44
|
+
} : T extends ReferenceIdentifierType ? string : T extends ChildEntitiesType ? never : T extends EnumType<infer EC> ? EC extends Record<string, EnumCaseDecl> ? {
|
|
45
|
+
[Case in keyof EC]: EnumCaseTypeAsDeepType<Case & string, EC[Case]["type"]>;
|
|
32
46
|
}[keyof EC] : never : never;
|
|
33
|
-
export type AsType<T extends Type> = T extends ArrayType<infer I> ? AsType<I>[] : T extends ObjectType<infer P> ? {
|
|
34
|
-
[
|
|
35
|
-
} : T extends
|
|
47
|
+
export type AsType<T extends Type> = T extends ArrayType<infer I> ? AsType<I>[] : T extends ObjectType<infer P> ? MemberDeclsAsType<P> : T extends BooleanType ? boolean : T extends DateType ? Date : T extends FloatType ? number : T extends IntegerType ? number : T extends StringType ? string : T extends TypeArgumentType ? unknown : T extends IncludeIdentifierType ? unknown : T extends NestedEntityMapType<string, infer TC> ? {
|
|
48
|
+
[id: string]: MemberDeclsAsType<TC>;
|
|
49
|
+
} : T extends ReferenceIdentifierType ? string : T extends ChildEntitiesType ? string[] : T extends EnumType<infer EC> ? EC extends Record<string, EnumCaseDecl> ? {
|
|
36
50
|
[Case in keyof EC]: EnumCaseTypeAsType<Case & string, EC[Case]["type"]>;
|
|
37
51
|
}[keyof EC] : never : never;
|
|
38
52
|
export type AsNode<T> = T extends (infer I)[] ? ArrayType<AsNode<I>> : T extends Record<string, unknown> ? ObjectType<{
|
|
@@ -2,10 +2,13 @@ import type { NumberConstraints } from "../../../../shared/validation/number.ts"
|
|
|
2
2
|
import type { GetNestedDeclarations, GetReferences, Predicate, Serializer, TypeArgumentsResolver, Validator } from "../../Node.ts";
|
|
3
3
|
import { NodeKind } from "../../Node.ts";
|
|
4
4
|
import type { BaseType, StructureFormatter } from "../Type.ts";
|
|
5
|
-
export interface
|
|
5
|
+
export interface FloatConstraints extends NumberConstraints {
|
|
6
|
+
fractionDigits?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface FloatType extends BaseType, FloatConstraints {
|
|
6
9
|
kind: NodeKind["FloatType"];
|
|
7
10
|
}
|
|
8
|
-
export declare const FloatType: (options?:
|
|
11
|
+
export declare const FloatType: (options?: FloatConstraints) => FloatType;
|
|
9
12
|
export { FloatType as Float };
|
|
10
13
|
export declare const isFloatType: Predicate<FloatType>;
|
|
11
14
|
export declare const getNestedDeclarationsInFloatType: GetNestedDeclarations<FloatType>;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { validateNumberConstraints } from "../../../../shared/validation/number.js";
|
|
2
2
|
import { json } from "../../../utils/errorFormatting.js";
|
|
3
3
|
import { NodeKind } from "../../Node.js";
|
|
4
|
-
export const FloatType = (options = {}) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export const FloatType = (options = {}) => {
|
|
5
|
+
if (options.fractionDigits !== undefined &&
|
|
6
|
+
(!Number.isInteger(options.fractionDigits) || options.fractionDigits < 1)) {
|
|
7
|
+
throw new TypeError("The fractionDigits option must be a positive integer");
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
...options,
|
|
11
|
+
kind: NodeKind.FloatType,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
8
14
|
export { FloatType as Float };
|
|
9
15
|
export const isFloatType = node => node.kind === NodeKind.FloatType;
|
|
10
16
|
export const getNestedDeclarationsInFloatType = addedDecls => addedDecls;
|
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { hasFileChanges, splitBranchName } from "../../../shared/utils/git.js";
|
|
5
5
|
import { getInstanceContainerOverview } from "../../../shared/utils/instances.js";
|
|
6
6
|
import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
|
|
7
|
+
import { attachGitStatusToDatabaseInMemory } from "../../utils/git.js";
|
|
7
8
|
import { reinit } from "../init.js";
|
|
8
9
|
import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
|
|
9
10
|
const debug = Debug("tsondb:server:api:git");
|
|
@@ -24,6 +25,9 @@ gitApi.get("/", (req, res) => {
|
|
|
24
25
|
});
|
|
25
26
|
gitApi.get("/status", async (req, res) => {
|
|
26
27
|
const status = await req.git.status();
|
|
28
|
+
req.setLocal("databaseInMemory", attachGitStatusToDatabaseInMemory(req.databaseInMemory, req.dataRoot,
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
30
|
+
req.gitRoot, status));
|
|
27
31
|
const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
|
|
28
32
|
const body = {
|
|
29
33
|
currentBranch: status.current,
|
|
@@ -2,10 +2,12 @@ import express from "express";
|
|
|
2
2
|
import { declarationsApi } from "./declarations.js";
|
|
3
3
|
import { gitApi } from "./git.js";
|
|
4
4
|
import { instancesApi } from "./instances.js";
|
|
5
|
+
import { searchApi } from "./search.js";
|
|
5
6
|
export const api = express.Router();
|
|
6
7
|
api.use("/declarations", declarationsApi);
|
|
7
8
|
api.use("/instances", instancesApi);
|
|
8
9
|
api.use("/git", gitApi);
|
|
10
|
+
api.use("/search", searchApi);
|
|
9
11
|
api.get("/config", (req, res) => {
|
|
10
12
|
const body = {
|
|
11
13
|
localeEntityName: req.localeEntity?.name,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const searchApi: import("express-serve-static-core").Router;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { isEntityDeclWithParentReference } from "../../schema/index.js";
|
|
4
|
+
import { getGroupedInstancesFromDatabaseInMemory } from "../../utils/databaseInMemory.js";
|
|
5
|
+
import { getDisplayNameFromEntityInstance } from "../../utils/displayName.js";
|
|
6
|
+
import { createChildInstancesForInstanceIdGetter } from "../utils/childInstances.js";
|
|
7
|
+
import { getQueryParamString } from "../utils/query.js";
|
|
8
|
+
const debug = Debug("tsondb:server:api:search");
|
|
9
|
+
export const searchApi = express.Router();
|
|
10
|
+
searchApi.get("/", (req, res) => {
|
|
11
|
+
const query = getQueryParamString(req.query, "q")?.toLowerCase() ?? "";
|
|
12
|
+
debug('search for items containing "%s"', query);
|
|
13
|
+
const getChildInstancesForInstanceId = createChildInstancesForInstanceIdGetter(req);
|
|
14
|
+
const body = {
|
|
15
|
+
query,
|
|
16
|
+
results: query.length === 0
|
|
17
|
+
? []
|
|
18
|
+
: getGroupedInstancesFromDatabaseInMemory(req.databaseInMemory)
|
|
19
|
+
.flatMap(([entityName, instances]) => {
|
|
20
|
+
const entity = req.entitiesByName[entityName];
|
|
21
|
+
if (entity && isEntityDeclWithParentReference(entity)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return instances
|
|
25
|
+
.map((instance) => {
|
|
26
|
+
const { name, localeId } = getDisplayNameFromEntityInstance(
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
28
|
+
req.entitiesByName[entityName], instance, req.getInstanceById, getChildInstancesForInstanceId, req.locales);
|
|
29
|
+
const searchableName = name.toLowerCase();
|
|
30
|
+
return [
|
|
31
|
+
entityName,
|
|
32
|
+
{
|
|
33
|
+
id: instance.id,
|
|
34
|
+
displayName: name,
|
|
35
|
+
displayNameLocaleId: localeId,
|
|
36
|
+
relevance: instance.id.startsWith(query) || searchableName.startsWith(query)
|
|
37
|
+
? 1
|
|
38
|
+
: instance.id.includes(query) || searchableName.includes(query)
|
|
39
|
+
? 0.5
|
|
40
|
+
: 0,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
})
|
|
44
|
+
.filter(instance => instance[1].relevance > 0);
|
|
45
|
+
})
|
|
46
|
+
.toSorted((a, b) => a[1].relevance - b[1].relevance ||
|
|
47
|
+
a[1].displayName.localeCompare(b[1].displayName, a[1].displayNameLocaleId)),
|
|
48
|
+
};
|
|
49
|
+
res.json(body);
|
|
50
|
+
});
|
|
@@ -8,7 +8,7 @@ export const createInstance = async (locals, instance, idQueryParam) => {
|
|
|
8
8
|
if (entity === undefined) {
|
|
9
9
|
return error(new HTTPError(400, "Entity not found"));
|
|
10
10
|
}
|
|
11
|
-
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, locals.localeEntity, instance.entityName, undefined, instance, idQueryParam, res));
|
|
11
|
+
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, undefined, instance, idQueryParam, res));
|
|
12
12
|
if (isError(databaseTransactionResult)) {
|
|
13
13
|
return databaseTransactionResult;
|
|
14
14
|
}
|
|
@@ -27,7 +27,7 @@ export const updateInstance = async (locals, instance) => {
|
|
|
27
27
|
return error(new HTTPError(400, "Entity not found"));
|
|
28
28
|
}
|
|
29
29
|
const oldChildInstances = getChildInstances(locals.databaseInMemory, entity, instance.id, true);
|
|
30
|
-
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, locals.localeEntity, instance.entityName, {
|
|
30
|
+
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, undefined, locals.localeEntity, instance.entityName, {
|
|
31
31
|
id: instance.id,
|
|
32
32
|
content: instanceContainer.content,
|
|
33
33
|
childInstances: oldChildInstances,
|
|
@@ -51,7 +51,7 @@ export const deleteInstance = async (locals, entityName, instanceId) => {
|
|
|
51
51
|
return error(new HTTPError(400, "Entity not found"));
|
|
52
52
|
}
|
|
53
53
|
const oldChildInstances = getChildInstances(locals.databaseInMemory, entity, instanceId, true);
|
|
54
|
-
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, locals.localeEntity, entityName, {
|
|
54
|
+
const databaseTransactionResult = await runDatabaseTransaction(locals.dataRoot, locals.gitRoot ? locals.git : undefined, locals.entitiesByName, locals.databaseInMemory, locals.referencesToInstances, res => saveInstanceTree(locals.entitiesByName, undefined, undefined, locals.localeEntity, entityName, {
|
|
55
55
|
id: instanceId,
|
|
56
56
|
content: instanceContainer.content,
|
|
57
57
|
childInstances: oldChildInstances,
|
|
@@ -29,6 +29,6 @@ export interface GenEntityTaggedInstanceContainerWithChildInstances<ID extends s
|
|
|
29
29
|
}
|
|
30
30
|
export declare const getChildInstancesFromEntity: (databaseInMemory: DatabaseInMemory, parentEntity: EntityDecl, parentId: string, childEntity: EntityDeclWithParentReference) => InstanceContainer[];
|
|
31
31
|
export declare const getChildInstances: (databaseInMemory: DatabaseInMemory, parentEntity: EntityDecl, parentId: string, recursive?: boolean) => EntityTaggedInstanceContainerWithChildInstances[];
|
|
32
|
-
export declare const saveInstanceTree: (entitiesByName: Record<string, EntityDecl>, parentId: string | undefined, localeEntity: EntityDecl | undefined, entityName: string, oldInstance: EntityTaggedInstanceContainerWithChildInstances | undefined, newInstance: UnsafeEntityTaggedInstanceContainerWithChildInstances | undefined, customId: unknown, res: TransactionResult) => TransactionResult<{
|
|
32
|
+
export declare const saveInstanceTree: (entitiesByName: Record<string, EntityDecl>, parentEntityName: string | undefined, parentId: string | undefined, localeEntity: EntityDecl | undefined, entityName: string, oldInstance: EntityTaggedInstanceContainerWithChildInstances | undefined, newInstance: UnsafeEntityTaggedInstanceContainerWithChildInstances | undefined, customId: unknown, res: TransactionResult) => TransactionResult<{
|
|
33
33
|
instanceContainer: InstanceContainer;
|
|
34
34
|
}>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { createEnumCaseValue } from "../../shared/schema/declarations/EnumDecl.js";
|
|
1
2
|
import { hasKey } from "../../shared/utils/object.js";
|
|
2
3
|
import { error, isError, map, ok } from "../../shared/utils/result.js";
|
|
3
|
-
import { isEntityDeclWithParentReference, reduceNodes } from "../schema/index.js";
|
|
4
|
+
import { isEntityDeclWithParentReference, isEnumDecl, isIncludeIdentifierType, reduceNodes, } from "../schema/index.js";
|
|
4
5
|
import { isChildEntitiesType } from "../schema/types/references/ChildEntitiesType.js";
|
|
5
6
|
import { getInstancesOfEntityFromDatabaseInMemory, } from "./databaseInMemory.js";
|
|
6
7
|
import { HTTPError } from "./error.js";
|
|
@@ -30,21 +31,26 @@ export const getChildInstances = (databaseInMemory, parentEntity, parentId, recu
|
|
|
30
31
|
: [],
|
|
31
32
|
})));
|
|
32
33
|
};
|
|
33
|
-
const prepareNewChildInstanceContent = (entity, parentId, content) => {
|
|
34
|
+
const prepareNewChildInstanceContent = (entity, parentEntityName, parentId, content) => {
|
|
34
35
|
if (isEntityDeclWithParentReference(entity)) {
|
|
35
|
-
if (parentId === undefined) {
|
|
36
|
+
if (parentEntityName === undefined || parentId === undefined) {
|
|
36
37
|
return error(new HTTPError(400, `Cannot create instance of child entity "${entity.name}" without parent reference`));
|
|
37
38
|
}
|
|
39
|
+
const parentReferenceType = entity.type.value.properties[entity.parentReferenceKey]?.type;
|
|
38
40
|
return ok({
|
|
39
41
|
...content,
|
|
40
|
-
[entity.parentReferenceKey]:
|
|
42
|
+
[entity.parentReferenceKey]: parentReferenceType &&
|
|
43
|
+
isIncludeIdentifierType(parentReferenceType) &&
|
|
44
|
+
isEnumDecl(parentReferenceType.reference)
|
|
45
|
+
? createEnumCaseValue(parentEntityName, parentId)
|
|
46
|
+
: parentId,
|
|
41
47
|
});
|
|
42
48
|
}
|
|
43
49
|
else {
|
|
44
50
|
return ok(content);
|
|
45
51
|
}
|
|
46
52
|
};
|
|
47
|
-
export const saveInstanceTree = (entitiesByName, parentId, localeEntity, entityName, oldInstance, newInstance, customId, res) => {
|
|
53
|
+
export const saveInstanceTree = (entitiesByName, parentEntityName, parentId, localeEntity, entityName, oldInstance, newInstance, customId, res) => {
|
|
48
54
|
if (isError(res)) {
|
|
49
55
|
return res;
|
|
50
56
|
}
|
|
@@ -59,14 +65,14 @@ export const saveInstanceTree = (entitiesByName, parentId, localeEntity, entityN
|
|
|
59
65
|
}
|
|
60
66
|
// delete all child instances recursively
|
|
61
67
|
const deletedRes = deleteInstance(res, entity, oldInstance.id);
|
|
62
|
-
return map(oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, oldInstance.id, localeEntity,
|
|
68
|
+
return map(oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, oldInstance.entityName, oldInstance.id, localeEntity, oldChildInstance.entityName, oldChildInstance, undefined, undefined, resAcc), deletedRes), data => ({
|
|
63
69
|
...data,
|
|
64
70
|
instanceContainer: { id: oldInstance.id, content: oldInstance.content },
|
|
65
71
|
}));
|
|
66
72
|
}
|
|
67
73
|
else {
|
|
68
74
|
const preparedContent = newInstance.id === undefined
|
|
69
|
-
? prepareNewChildInstanceContent(entity, parentId, newInstance.content)
|
|
75
|
+
? prepareNewChildInstanceContent(entity, parentEntityName, parentId, newInstance.content)
|
|
70
76
|
: ok(newInstance.content);
|
|
71
77
|
if (isError(preparedContent)) {
|
|
72
78
|
return preparedContent;
|
|
@@ -85,8 +91,8 @@ export const saveInstanceTree = (entitiesByName, parentId, localeEntity, entityN
|
|
|
85
91
|
const setResWithoutInfo = ok({ ...setRes.value, additionalInformation: undefined });
|
|
86
92
|
return map(newInstance.childInstances
|
|
87
93
|
.filter(newChildInstance => newChildInstance.id === undefined)
|
|
88
|
-
.reduce((resAcc, newChildInstance) => saveInstanceTree(entitiesByName, instanceId, localeEntity, newChildInstance.entityName, undefined, newChildInstance, undefined, resAcc), oldInstance
|
|
89
|
-
? oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, instanceId, localeEntity, oldChildInstance.entityName, oldChildInstance, newInstance.childInstances.find(ci => ci.id === oldChildInstance.id), undefined, resAcc), setResWithoutInfo)
|
|
94
|
+
.reduce((resAcc, newChildInstance) => saveInstanceTree(entitiesByName, newInstance.entityName, instanceId, localeEntity, newChildInstance.entityName, undefined, newChildInstance, undefined, resAcc), oldInstance
|
|
95
|
+
? oldInstance.childInstances.reduce((resAcc, oldChildInstance) => saveInstanceTree(entitiesByName, oldInstance.entityName, instanceId, localeEntity, oldChildInstance.entityName, oldChildInstance, newInstance.childInstances.find(ci => ci.id === oldChildInstance.id), undefined, resAcc), setResWithoutInfo)
|
|
90
96
|
: setResWithoutInfo), data => ({
|
|
91
97
|
...data,
|
|
92
98
|
instanceContainer: { id: instanceId, content: preparedContent.value },
|
|
@@ -4,6 +4,7 @@ export const getDisplayNameFromEntityInstance = (entity, instanceContainer, getI
|
|
|
4
4
|
if (useCustomizer && entity.displayNameCustomizer) {
|
|
5
5
|
const calculatedName = getDisplayNameFromEntityInstance(entity, instanceContainer, getInstanceById, getChildInstancesForInstanceId, locales, defaultName, false);
|
|
6
6
|
return entity.displayNameCustomizer({
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment -- otherwise type instiatiation too deep
|
|
7
8
|
instance: instanceContainer.content,
|
|
8
9
|
instanceId: instanceContainer.id,
|
|
9
10
|
instanceDisplayName: calculatedName.name,
|
package/dist/src/shared/api.d.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GenericEntityDisplayName } from "../../../node/schema/index.ts";
|
|
2
2
|
import { NodeKind, type GetReferencesSerialized, type SerializedNode, type SerializedTypeArgumentsResolver } from "../Node.ts";
|
|
3
|
+
import type { SerializedNestedEntityMapType } from "../types/NestedEntityMapType.ts";
|
|
3
4
|
import { type SerializedMemberDecl, type SerializedObjectType } from "../types/ObjectType.ts";
|
|
4
|
-
import type {
|
|
5
|
+
import type { SerializedStringType } from "../types/StringType.ts";
|
|
5
6
|
import type { SerializedBaseDecl } from "./Declaration.ts";
|
|
6
|
-
export type SerializedEntityDisplayName<T extends
|
|
7
|
+
export type SerializedEntityDisplayName<T extends TSerializedConstraint> = SerializedPathTo<T, SerializedStringType> | {
|
|
7
8
|
/**
|
|
8
9
|
* @default "translations"
|
|
9
10
|
*/
|
|
10
|
-
pathToLocaleMap?:
|
|
11
|
+
pathToLocaleMap?: SerializedPathTo<T, SerializedNestedEntityMapType>;
|
|
11
12
|
/**
|
|
12
13
|
* @default "name"
|
|
13
14
|
*/
|
|
14
15
|
pathInLocaleMap?: string;
|
|
15
16
|
} | null;
|
|
16
|
-
type
|
|
17
|
-
|
|
17
|
+
type TSerializedConstraint = Record<string, SerializedMemberDecl>;
|
|
18
|
+
type SerializedPathTo<T extends TSerializedConstraint, R> = {
|
|
19
|
+
[K in keyof T]: T[K] extends SerializedMemberDecl<infer V> ? V extends R ? K : R extends V ? string : T[K] extends SerializedObjectType<infer P> ? `${Extract<K, string>}.${SerializedPathTo<P, R>}` : never : never;
|
|
20
|
+
}[Extract<keyof T, string>];
|
|
21
|
+
export interface SerializedEntityDecl<Name extends string = string, T extends TSerializedConstraint = TSerializedConstraint, FK extends Extract<keyof T, string> | undefined = Extract<keyof T, string> | undefined> extends SerializedBaseDecl<Name, []> {
|
|
18
22
|
kind: NodeKind["EntityDecl"];
|
|
19
23
|
namePlural: string;
|
|
20
24
|
type: SerializedObjectType<T>;
|
|
@@ -22,13 +26,13 @@ export interface SerializedEntityDecl<Name extends string = string, T extends TC
|
|
|
22
26
|
/**
|
|
23
27
|
* @default "name"
|
|
24
28
|
*/
|
|
25
|
-
displayName?:
|
|
29
|
+
displayName?: GenericEntityDisplayName;
|
|
26
30
|
displayNameCustomizer: boolean;
|
|
27
31
|
isDeprecated?: boolean;
|
|
28
32
|
}
|
|
29
33
|
export declare const isSerializedEntityDecl: (node: SerializedNode) => node is SerializedEntityDecl;
|
|
30
|
-
export declare const isSerializedEntityDeclWithParentReference: <Name extends string, T extends
|
|
31
|
-
export declare const isSerializedEntityDeclWithoutParentReference: <Name extends string, T extends
|
|
34
|
+
export declare const isSerializedEntityDeclWithParentReference: <Name extends string, T extends TSerializedConstraint, FK extends Extract<keyof T, string> | undefined>(decl: SerializedEntityDecl<Name, T, FK>) => decl is SerializedEntityDecl<Name, T, NonNullable<FK>>;
|
|
35
|
+
export declare const isSerializedEntityDeclWithoutParentReference: <Name extends string, T extends TSerializedConstraint>(decl: SerializedEntityDecl<Name, T>) => decl is SerializedEntityDecl<Name, T, undefined>;
|
|
32
36
|
export declare const resolveTypeArgumentsInSerializedEntityDecl: SerializedTypeArgumentsResolver<SerializedEntityDecl>;
|
|
33
37
|
export declare const getReferencesForSerializedEntityDecl: GetReferencesSerialized<SerializedEntityDecl>;
|
|
34
38
|
export {};
|
|
@@ -10,3 +10,11 @@ export interface SerializedEnumDecl<Name extends string = string, T extends Reco
|
|
|
10
10
|
export declare const isSerializedEnumDecl: (node: SerializedNode) => node is SerializedEnumDecl;
|
|
11
11
|
export declare const resolveTypeArgumentsInSerializedEnumDecl: SerializedTypeArgumentsResolver<SerializedEnumDecl>;
|
|
12
12
|
export declare const getReferencesForSerializedEnumDecl: GetReferencesSerialized<SerializedEnumDecl>;
|
|
13
|
+
export declare const ENUM_DISCRIMINATOR_KEY = "kind";
|
|
14
|
+
export type ENUM_DISCRIMINATOR_KEY = typeof ENUM_DISCRIMINATOR_KEY;
|
|
15
|
+
export type EnumValue<K extends string, V> = {
|
|
16
|
+
[ENUM_DISCRIMINATOR_KEY]: K;
|
|
17
|
+
} & {
|
|
18
|
+
[Key2 in K]: V;
|
|
19
|
+
};
|
|
20
|
+
export declare const createEnumCaseValue: <K extends string, V>(caseName: K, caseValue: V) => EnumValue<K, V>;
|
|
@@ -9,3 +9,8 @@ export const resolveTypeArgumentsInSerializedEnumDecl = (decls, args, decl) => {
|
|
|
9
9
|
};
|
|
10
10
|
};
|
|
11
11
|
export const getReferencesForSerializedEnumDecl = (decls, decl, value) => getReferencesForSerializedEnumType(decls, decl.type, value);
|
|
12
|
+
export const ENUM_DISCRIMINATOR_KEY = "kind";
|
|
13
|
+
export const createEnumCaseValue = (caseName, caseValue) => ({
|
|
14
|
+
[ENUM_DISCRIMINATOR_KEY]: caseName,
|
|
15
|
+
[caseName]: caseValue,
|
|
16
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NumberConstraints } from "../../validation/number.ts";
|
|
2
2
|
import type { GetReferencesSerialized, NodeKind, SerializedTypeArgumentsResolver } from "../Node.ts";
|
|
3
3
|
import type { SerializedBaseType } from "./Type.ts";
|
|
4
|
-
export interface
|
|
4
|
+
export interface FloatConstraints extends NumberConstraints {
|
|
5
|
+
fractionDigits?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_FRACTION_DIGITS = 2;
|
|
8
|
+
export interface SerializedFloatType extends SerializedBaseType, FloatConstraints {
|
|
5
9
|
kind: NodeKind["FloatType"];
|
|
6
|
-
minimum?: RangeBound;
|
|
7
|
-
maximum?: RangeBound;
|
|
8
|
-
multipleOf?: number;
|
|
9
10
|
}
|
|
10
11
|
export declare const resolveTypeArgumentsInSerializedFloatType: SerializedTypeArgumentsResolver<SerializedFloatType>;
|
|
11
12
|
export declare const getReferencesForSerializedFloatType: GetReferencesSerialized<SerializedFloatType>;
|
|
@@ -2,7 +2,7 @@ export declare const sortObjectKeys: (obj: Record<string, unknown>, keys: string
|
|
|
2
2
|
export declare const sortObjectKeysAlphabetically: (obj: Record<string, unknown>) => Record<string, unknown>;
|
|
3
3
|
export declare const mergeObjects: <T>(obj1: Record<string, T>, obj2: Record<string, T>, solveConflict: (a: T, b: T) => T) => Record<string, T>;
|
|
4
4
|
export type Leaves<T> = T extends object ? {
|
|
5
|
-
[K in keyof T]: T[K] extends unknown[] ? never : `${
|
|
5
|
+
[K in keyof T]: T[K] extends unknown[] ? never : `${Extract<K, string>}${Leaves<T[K]> extends never ? "" : `.${Leaves<T[K]>}`}`;
|
|
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; };
|
|
@@ -6,7 +6,7 @@ import { Settings } from "./Settings.js";
|
|
|
6
6
|
export const Layout = ({ breadcrumbs, children }) => {
|
|
7
7
|
const [_1, setIsGitOpen] = useContext(GitContext);
|
|
8
8
|
const [isGitAlwaysOpen, _2] = useSetting("gitSidebar");
|
|
9
|
-
return (_jsxs(_Fragment, { children: [_jsxs("header", { children: [_jsx("nav", { children: _jsx("ol", { children: breadcrumbs.map(({ url, label }) => (_jsx("li", { children: _jsx("a", { href: url, children: label }) }, url))) }) }), _jsxs("div", { class: "nav-buttons", children: [_jsx("button", { class: `git-toggle${!isGitAlwaysOpen ? " git-toggle--no-sidebar" : ""}`, onClick: () => {
|
|
9
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { children: [_jsx("nav", { children: _jsx("ol", { children: breadcrumbs.map(({ url, label }) => (_jsx("li", { children: _jsx("a", { href: url, children: label }) }, url))) }) }), _jsxs("div", { class: "nav-buttons", children: [_jsx("a", { href: "/search", class: "btn", children: "Search" }), _jsx("button", { class: `git-toggle${!isGitAlwaysOpen ? " git-toggle--no-sidebar" : ""}`, onClick: () => {
|
|
10
10
|
setIsGitOpen(b => !b);
|
|
11
11
|
}, children: "File changes" }), _jsx(Settings, {})] })] }), _jsx("main", { children: children })] }));
|
|
12
12
|
};
|
|
@@ -2,5 +2,5 @@ import { jsx as _jsx } from "preact/jsx-runtime";
|
|
|
2
2
|
import { getGitStatusForDisplay, getLabelForGitStatus, } from "../../../shared/utils/git.js";
|
|
3
3
|
export const GitStatusIndicator = ({ status }) => {
|
|
4
4
|
const gitStatusForDisplay = getGitStatusForDisplay(status);
|
|
5
|
-
return (_jsx("span", { class: `git-status git-status--${gitStatusForDisplay
|
|
5
|
+
return gitStatusForDisplay === undefined ? null : (_jsx("span", { class: `git-status git-status--${gitStatusForDisplay}`, title: getLabelForGitStatus(gitStatusForDisplay), children: gitStatusForDisplay }));
|
|
6
6
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FunctionComponent } from "preact";
|
|
2
|
-
import type
|
|
2
|
+
import { type SerializedFloatType } from "../../../shared/schema/types/FloatType.ts";
|
|
3
3
|
import type { TypeInputProps } from "./TypeInput.tsx";
|
|
4
4
|
type Props = TypeInputProps<SerializedFloatType, number>;
|
|
5
5
|
export declare const FloatTypeInput: FunctionComponent<Props>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
2
|
import { useState } from "preact/hooks";
|
|
3
|
+
import { DEFAULT_FRACTION_DIGITS, } from "../../../shared/schema/types/FloatType.js";
|
|
3
4
|
import { validateNumberConstraints } from "../../../shared/validation/number.js";
|
|
4
5
|
import { MismatchingTypeError } from "./utils/MismatchingTypeError.js";
|
|
5
6
|
import { ValidationErrors } from "./utils/ValidationErrors.js";
|
|
@@ -15,5 +16,5 @@ export const FloatTypeInput = ({ type, value, disabled, onChange }) => {
|
|
|
15
16
|
if (!Number.isNaN(numericValue)) {
|
|
16
17
|
onChange(numericValue);
|
|
17
18
|
}
|
|
18
|
-
}, step:
|
|
19
|
+
}, step: 1 / Math.pow(10, type.fractionDigits ?? DEFAULT_FRACTION_DIGITS), "aria-invalid": errors.length > 0, disabled: disabled }), _jsx(ValidationErrors, { disabled: disabled, errors: errors })] }));
|
|
19
20
|
};
|
package/dist/src/web/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { Entity } from "./routes/Entity.js";
|
|
|
20
20
|
import { Home } from "./routes/Home.js";
|
|
21
21
|
import { Instance } from "./routes/Instance.js";
|
|
22
22
|
import { NotFound } from "./routes/NotFound.js";
|
|
23
|
+
import { Search } from "./routes/Search.js";
|
|
23
24
|
const mapEntities = (data) => data.declarations
|
|
24
25
|
.map(decl => ({ ...decl, isLocaleEntity: decl.declaration.name === data.localeEntity }))
|
|
25
26
|
.sort((a, b) => a.declaration.name.localeCompare(b.declaration.name));
|
|
@@ -33,7 +34,7 @@ const App = ({ config }) => {
|
|
|
33
34
|
alert("Error reloading entities: " + String(error));
|
|
34
35
|
});
|
|
35
36
|
}, [location.path, reloadEntities]);
|
|
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, {})] }) }) }) }) }) }));
|
|
37
|
+
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: "/search", component: Search }), _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, {})] }) }) }) }) }) }));
|
|
37
38
|
};
|
|
38
39
|
const config = await getWebConfig();
|
|
39
40
|
const root = document.getElementById("app");
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "preact/hooks";
|
|
3
|
+
import { getGitStatusForDisplay } from "../../shared/utils/git.js";
|
|
4
|
+
import { toTitleCase } from "../../shared/utils/string.js";
|
|
5
|
+
import { deleteInstanceByEntityNameAndId } from "../api/declarations.js";
|
|
6
|
+
import { searchInstances } from "../api/search.js";
|
|
7
|
+
import { GitStatusIndicator } from "../components/git/GitStatusIndicator.js";
|
|
8
|
+
import { Layout } from "../components/Layout.js";
|
|
9
|
+
import { useSetting } from "../hooks/useSettings.js";
|
|
10
|
+
import { logAndAlertError } from "../utils/debug.js";
|
|
11
|
+
import { homeTitle } from "./Home.js";
|
|
12
|
+
const MIN_CHARACTERS = 3;
|
|
13
|
+
export const Search = () => {
|
|
14
|
+
const [locales] = useSetting("displayedLocales");
|
|
15
|
+
const [query, setQuery] = useState(() => new URLSearchParams(window.location.search).get("q") ?? "");
|
|
16
|
+
const [results, setResults] = useState();
|
|
17
|
+
const search = useCallback(() => {
|
|
18
|
+
const url = new URL(window.location.href);
|
|
19
|
+
if (url.searchParams.get("q") !== query) {
|
|
20
|
+
if (query.length === 0) {
|
|
21
|
+
url.searchParams.delete("q");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
url.searchParams.set("q", query);
|
|
25
|
+
}
|
|
26
|
+
window.history.pushState({}, "", url);
|
|
27
|
+
}
|
|
28
|
+
if (query.length >= MIN_CHARACTERS) {
|
|
29
|
+
searchInstances(locales, query)
|
|
30
|
+
.then(res => {
|
|
31
|
+
setResults(res.results);
|
|
32
|
+
})
|
|
33
|
+
.catch(logAndAlertError);
|
|
34
|
+
}
|
|
35
|
+
}, [locales, query]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
document.title = "Search — TSONDB";
|
|
38
|
+
search();
|
|
39
|
+
}, [search]);
|
|
40
|
+
return (_jsxs(Layout, { breadcrumbs: [{ url: "/", label: homeTitle }], children: [_jsx("h1", { children: "Search" }), _jsx("input", { type: "search", name: "q", value: query, onInput: event => {
|
|
41
|
+
setQuery(event.currentTarget.value);
|
|
42
|
+
} }), query.length < MIN_CHARACTERS ? (_jsx("p", { class: "help", children: "Provide at least 3 characters in the search field to start the search" })) : (results && (_jsxs("section", { class: "search-results", children: [_jsx("h2", { children: "Results" }), results.length === 0 ? (_jsx("p", { class: "empty", children: "No results" })) : (_jsx("ul", { class: "entries entries--instances", children: results.map(([entityName, instance]) => {
|
|
43
|
+
const gitStatusForDisplay = getGitStatusForDisplay(instance.gitStatus);
|
|
44
|
+
return (_jsxs("li", { id: `instance-${instance.id}`, class: `entries-item${gitStatusForDisplay === undefined
|
|
45
|
+
? ""
|
|
46
|
+
: ` git-status--${gitStatusForDisplay}`}`, children: [_jsxs("h2", { class: "entries-item__title", children: [instance.displayName, _jsx("span", { "aria-hidden": true, class: "entries-item__title-entity", children: toTitleCase(entityName) })] }), _jsx("p", { "aria-hidden": true, class: "entries-item__subtitle entries-item__subtitle--id", children: instance.id }), _jsxs("div", { class: "entries-item__side", children: [_jsx(GitStatusIndicator, { status: instance.gitStatus }), _jsxs("div", { class: "btns", children: [_jsx("a", { href: `/entities/${entityName}/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(locales, entityName, instance.id)
|
|
49
|
+
.then(() => {
|
|
50
|
+
search();
|
|
51
|
+
})
|
|
52
|
+
.catch(logAndAlertError);
|
|
53
|
+
}
|
|
54
|
+
}, children: "Delete" })] })] })] }, instance.id));
|
|
55
|
+
}) }))] })))] }));
|
|
56
|
+
};
|
package/package.json
CHANGED
package/public/css/styles.css
CHANGED
|
@@ -589,6 +589,12 @@ select {
|
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
.entries-item__title-entity {
|
|
593
|
+
font-weight: 400;
|
|
594
|
+
color: var(--color-foreground-secondary);
|
|
595
|
+
padding-left: 0.5em;
|
|
596
|
+
}
|
|
597
|
+
|
|
592
598
|
.entries-item__subtitle--id {
|
|
593
599
|
font-family: var(--font-mono);
|
|
594
600
|
}
|
|
@@ -1205,3 +1211,7 @@ dialog[open]:has(> .loading-overlay--open)::backdrop {
|
|
|
1205
1211
|
.object-item--translation button {
|
|
1206
1212
|
align-self: center;
|
|
1207
1213
|
}
|
|
1214
|
+
|
|
1215
|
+
section.search-results {
|
|
1216
|
+
margin-top: 3.6rem;
|
|
1217
|
+
}
|