tsondb 0.21.2 → 0.21.4

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 (33) hide show
  1. package/dist/src/node/git.js +1 -1
  2. package/dist/src/node/index.d.ts +18 -2
  3. package/dist/src/node/index.js +28 -1
  4. package/dist/src/node/renderers/ts/render.js +1 -1
  5. package/dist/src/node/schema/index.js +7 -0
  6. package/dist/src/node/schema/treeOperations/validation.js +1 -1
  7. package/dist/src/node/server/api/declarations.js +44 -0
  8. package/dist/src/node/server/utils/instanceOperations.js +1 -1
  9. package/dist/src/node/transaction.d.ts +5 -0
  10. package/dist/src/node/transaction.js +49 -1
  11. package/dist/src/node/utils/childInstances.js +1 -1
  12. package/dist/src/node/utils/databaseInMemory.d.ts +8 -0
  13. package/dist/src/node/utils/databaseInMemory.js +59 -0
  14. package/dist/src/node/utils/error.d.ts +0 -8
  15. package/dist/src/node/utils/error.js +1 -39
  16. package/dist/src/node/utils/sortOrder.d.ts +13 -0
  17. package/dist/src/node/utils/sortOrder.js +12 -0
  18. package/dist/src/node/utils/unique.d.ts +0 -4
  19. package/dist/src/node/utils/unique.js +1 -7
  20. package/dist/src/shared/api.d.ts +18 -0
  21. package/dist/src/shared/schema/utils/keyPath.d.ts +1 -0
  22. package/dist/src/shared/schema/utils/keyPath.js +30 -0
  23. package/dist/src/shared/schema/utils/sortOrder.d.ts +9 -0
  24. package/dist/src/shared/schema/utils/sortOrder.js +11 -6
  25. package/dist/src/shared/utils/error.d.ts +8 -0
  26. package/dist/src/shared/utils/error.js +39 -0
  27. package/dist/src/{node → shared}/utils/render.js +1 -1
  28. package/dist/src/shared/utils/unique.d.ts +4 -0
  29. package/dist/src/shared/utils/unique.js +7 -0
  30. package/dist/src/web/web.js +93 -14
  31. package/package.json +1 -1
  32. package/public/css/styles.css +3 -2
  33. /package/dist/src/{node → shared}/utils/render.d.ts +0 -0
@@ -1,7 +1,7 @@
1
1
  import Debug from "debug";
2
2
  import { join } from "path";
3
+ import { HTTPError } from "../shared/utils/error.js";
3
4
  import { getAllInstanceOverviewsByEntityName } from "./utils/displayName.js";
4
- import { HTTPError } from "./utils/error.js";
5
5
  import { getFileNameForId } from "./utils/files.js";
6
6
  import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
7
7
  const debug = Debug("tsondb:git");
@@ -1,4 +1,4 @@
1
- import type { InstanceContainer, InstanceContainerOverview, SingletonInstanceContainer } from "../shared/utils/instances.ts";
1
+ import type { InstanceContainer, InstanceContainerOverview, InstanceContent, SingletonInstanceContainer } from "../shared/utils/instances.ts";
2
2
  import type { FormatterOptions } from "./config.ts";
3
3
  import { Git } from "./git.js";
4
4
  import type { Output } from "./output.ts";
@@ -14,7 +14,7 @@ export interface DefaultTSONDBTypes {
14
14
  enumMap: AnyEnumMap;
15
15
  typeAliasMap: AnyTypeAliasMap;
16
16
  }
17
- export type Entity<T extends DefaultTSONDBTypes, E extends EntityName<T>> = T["entityMap"][E];
17
+ export type Entity<T extends DefaultTSONDBTypes, E extends EntityName<T> = EntityName<T>> = T["entityMap"][E];
18
18
  export type EntityName<T extends DefaultTSONDBTypes> = Extract<keyof T["entityMap"], string>;
19
19
  export type SingletonEntity<T extends DefaultTSONDBTypes, E extends SingletonEntityName<T>> = T["singletonEntityMap"][E];
20
20
  export type SingletonEntityName<T extends DefaultTSONDBTypes> = Extract<keyof T["singletonEntityMap"], string>;
@@ -191,6 +191,22 @@ export declare class TSONDB<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
191
191
  * @throws {Error} when the instance could not be deleted
192
192
  */
193
193
  deleteSingletonInstance<E extends SingletonEntityName<T>>(entityName: E): Promise<SingletonEntity<T, E>>;
194
+ /**
195
+ * Reorders all instances of an entity in the database.
196
+ *
197
+ * The entity must have a custom sort order via an integer field defined.
198
+ *
199
+ * @throws {Error}
200
+ */
201
+ reorderAllInstancesOfEntity(entityName: EntityName<T> | EntityDecl<EntityName<T>>, newOrder: string[]): Promise<void>;
202
+ /**
203
+ * Reorders an instance of an entity in the database.
204
+ *
205
+ * The entity must have a custom sort order via an integer field defined.
206
+ *
207
+ * @throws {Error}
208
+ */
209
+ reorderInstanceOfEntity(entityName: EntityName<T> | EntityDecl<EntityName<T>>, instanceId: string, newIndex: number): Promise<[id: string, oldContent: InstanceContent, newContent: InstanceContent][]>;
194
210
  /**
195
211
  * Retrieves an instance of the specified entity by its identifier.
196
212
  */
@@ -11,6 +11,7 @@ import { stderr } from "node:process";
11
11
  import { styleText } from "node:util";
12
12
  import { simpleGit } from "simple-git";
13
13
  import { NodeKind } from "../shared/schema/Node.js";
14
+ import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErrorsIfAny, } from "../shared/utils/error.js";
14
15
  import { parallelizeErrors } from "../shared/utils/validation.js";
15
16
  import { Git } from "./git.js";
16
17
  import { getDisplayName, getDisplayNameWithId } from "./schema/detached.js";
@@ -24,7 +25,6 @@ import { checkCustomConstraintsForAllEntities } from "./utils/customConstraints.
24
25
  import { DatabaseInMemory } from "./utils/databaseInMemory.js";
25
26
  import { applyStepsToDisk } from "./utils/databaseOnDisk.js";
26
27
  import { getAllInstanceOverviewsByEntityName, getDisplayNameFromEntityInstance, getInstanceOverview, getInstanceOverviewsByEntityName, } from "./utils/displayName.js";
27
- import { countError, countErrors, getErrorMessageForDisplay, HTTPError, wrapErrorsIfAny, } from "./utils/error.js";
28
28
  import { formatInstance, getFileNameForId, getSingletonFileName, readInstance, readSingletonInstance, writeInstance, writeSingletonInstance, } from "./utils/files.js";
29
29
  import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
30
30
  import { getReferencesToInstances, isReferencedByOtherInstances, } from "./utils/references.js";
@@ -509,6 +509,33 @@ export class TSONDB {
509
509
  txn.deleteSingletonInstance(this.#schema.getSingletonEntity(entityName)));
510
510
  return res.content;
511
511
  }
512
+ /**
513
+ * Reorders all instances of an entity in the database.
514
+ *
515
+ * The entity must have a custom sort order via an integer field defined.
516
+ *
517
+ * @throws {Error}
518
+ */
519
+ async reorderAllInstancesOfEntity(entityName, newOrder) {
520
+ return this.runTransaction(txn => [
521
+ txn.reorderAllInstancesOfEntity(
522
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
523
+ typeof entityName === "string" ? this.#schema.getEntity(entityName) : entityName, newOrder),
524
+ undefined,
525
+ ]);
526
+ }
527
+ /**
528
+ * Reorders an instance of an entity in the database.
529
+ *
530
+ * The entity must have a custom sort order via an integer field defined.
531
+ *
532
+ * @throws {Error}
533
+ */
534
+ async reorderInstanceOfEntity(entityName, instanceId, newIndex) {
535
+ return this.runTransaction(txn => txn.reorderInstanceOfEntity(
536
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
537
+ typeof entityName === "string" ? this.#schema.getEntity(entityName) : entityName, instanceId, newIndex));
538
+ }
512
539
  /**
513
540
  * Retrieves an instance of the specified entity by its identifier.
514
541
  */
@@ -5,6 +5,7 @@ import { EOL } from "node:os";
5
5
  import { dirname, relative } from "node:path";
6
6
  import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
7
7
  import { NodeKind } from "../../../shared/schema/Node.js";
8
+ import { combineSyntaxes, emptyRenderResult, getIndentation, indent, prefixLines, syntax, } from "../../../shared/utils/render.js";
8
9
  import { extractParameterTypeNamesFromMessage, mapParameterTypeNames, } from "../../../shared/utils/translation.js";
9
10
  import { asDecl } from "../../schema/dsl/declarations/Decl.js";
10
11
  import { addEphemeralUUIDToType, createEntityIdentifierTypeAsDecl, isEntityDecl, isEntityDeclWithParentReference, } from "../../schema/dsl/declarations/EntityDecl.js";
@@ -16,7 +17,6 @@ import { isObjectType } from "../../schema/dsl/types/ObjectType.js";
16
17
  import { getTypeOfKey } from "../../schema/dsl/types/TranslationObjectType.js";
17
18
  import { flatMapAuxiliaryDecls, isFinalChildEntitiesType } from "../../schema/helpers.js";
18
19
  import { ensureSpecialDirStart } from "../../utils/path.js";
19
- import { combineSyntaxes, emptyRenderResult, getIndentation, indent, prefixLines, syntax, } from "../../utils/render.js";
20
20
  const defaultOptions = {
21
21
  indentation: 2,
22
22
  objectTypeKeyword: "interface",
@@ -440,6 +440,13 @@ const checkSortOrders = (declarations) => {
440
440
  if (!isValidSortOrderType(type)) {
441
441
  throw TypeError(`type at key path "${renderKeyPath(sortOrder.keyPath)}" is not a valid sort order type (string, integer, or float) in entity "${decl.name}"`);
442
442
  }
443
+ if (sortOrder.isIndex &&
444
+ (!isIntegerType(type) ||
445
+ type.maximum !== undefined ||
446
+ (typeof type.minimum === "object" && type.minimum.isExclusive) ||
447
+ type.multipleOf !== undefined)) {
448
+ throw TypeError(`type at key path ${renderKeyPath(sortOrder.keyPath)} is not a valid index sort order type (integer, optional inclusive minimum, no other constraints) in entity "${decl.name}"`);
449
+ }
443
450
  }
444
451
  else {
445
452
  // const entityMapType = getType(decl.type.value, sortOrder.entityMapKeyPath)
@@ -4,6 +4,7 @@ import { assertExhaustive } from "@elyukai/utils/typeSafety";
4
4
  import { MessageError, parseMessage, validate } from "messageformat";
5
5
  import { ENUM_DISCRIMINATOR_KEY } from "../../../shared/schema/declarations/EnumDecl.js";
6
6
  import { NodeKind } from "../../../shared/schema/Node.js";
7
+ import { wrapErrorsIfAny } from "../../../shared/utils/error.js";
7
8
  import { extendsParameterTypes, extractParameterTypeNamesFromMessage, } from "../../../shared/utils/translation.js";
8
9
  import { parallelizeErrors } from "../../../shared/utils/validation.js";
9
10
  import { validateArrayConstraints } from "../../../shared/validation/array.js";
@@ -12,7 +13,6 @@ import { validateNumberConstraints } from "../../../shared/validation/number.js"
12
13
  import { validateObjectConstraints, validateObjectRangeConstraints, validateUnknownKeys, } from "../../../shared/validation/object.js";
13
14
  import { validateStringConstraints } from "../../../shared/validation/string.js";
14
15
  import {} from "../../utils/databaseInMemory.js";
15
- import { wrapErrorsIfAny } from "../../utils/error.js";
16
16
  import { entity, json, key } from "../../utils/errorFormatting.js";
17
17
  import { getTypeArgumentsRecord } from "../dsl/declarations/Decl.js";
18
18
  import { createEntityIdentifierType } from "../dsl/declarations/EntityDecl.js";
@@ -148,6 +148,50 @@ declarationsApi.post("/:name/instances", async (req, res) => {
148
148
  sendErrorResponse(res, err);
149
149
  }
150
150
  });
151
+ declarationsApi.put("/:name/instances/reorder", async (req, res) => {
152
+ const decl = req.db.schema.getResolvedDeclaration(req.params.name);
153
+ if (decl === undefined) {
154
+ res.status(404).send(`Declaration "${req.params.name}" not found`);
155
+ return;
156
+ }
157
+ if (!isEntityDecl(decl)) {
158
+ res.status(400).send(`Declaration "${decl.name}" is not an entity`);
159
+ return;
160
+ }
161
+ try {
162
+ const requestBody = req.body;
163
+ await req.db.reorderAllInstancesOfEntity(decl, requestBody.order);
164
+ const body = {
165
+ instances: req.db.getAllInstanceOverviewsOfEntity(decl.name),
166
+ };
167
+ res.json(body);
168
+ }
169
+ catch (err) {
170
+ sendErrorResponse(res, err);
171
+ }
172
+ });
173
+ declarationsApi.put("/:name/instances/reorder/:id", async (req, res) => {
174
+ const decl = req.db.schema.getResolvedDeclaration(req.params.name);
175
+ if (decl === undefined) {
176
+ res.status(404).send(`Declaration "${req.params.name}" not found`);
177
+ return;
178
+ }
179
+ if (!isEntityDecl(decl)) {
180
+ res.status(400).send(`Declaration "${decl.name}" is not an entity`);
181
+ return;
182
+ }
183
+ try {
184
+ const requestBody = req.body;
185
+ await req.db.reorderInstanceOfEntity(decl, req.params.id, requestBody.targetIndex);
186
+ const body = {
187
+ instances: req.db.getAllInstanceOverviewsOfEntity(decl.name),
188
+ };
189
+ res.json(body);
190
+ }
191
+ catch (err) {
192
+ sendErrorResponse(res, err);
193
+ }
194
+ });
151
195
  declarationsApi.get("/:name/instances/:id", (req, res) => {
152
196
  const decl = req.db.schema.getResolvedDeclaration(req.params.name);
153
197
  if (decl === undefined) {
@@ -1,5 +1,5 @@
1
+ import { HTTPError } from "../../../shared/utils/error.js";
1
2
  import { getChildInstances, saveInstanceTree } from "../../utils/childInstances.js";
2
- import { HTTPError } from "../../utils/error.js";
3
3
  export const createInstance = async (db, instance, idQueryParam) => {
4
4
  const getEntity = db.schema.getEntity.bind(db.schema);
5
5
  const entity = getEntity(instance.entityName);
@@ -52,6 +52,11 @@ export declare class Transaction<EM extends AnyEntityMap = RegisteredEntityMap,
52
52
  createSingletonInstance(entity: SingletonEntityDecl<Extract<keyof SEM, string>>, instanceContent: InstanceContent): [Transaction<EM, SEM>, SingletonInstanceContainer];
53
53
  updateSingletonInstance(entity: SingletonEntityDecl<Extract<keyof SEM, string>>, instanceContent: InstanceContent): [Transaction<EM, SEM>, SingletonInstanceContainer];
54
54
  deleteSingletonInstance(entity: SingletonEntityDecl<Extract<keyof SEM, string>>): [Transaction<EM, SEM>, SingletonInstanceContainer];
55
+ reorderAllInstancesOfEntity(entity: EntityDecl<Extract<keyof EM, string>>, newOrder: string[]): Transaction<EM, SEM>;
56
+ reorderInstanceOfEntity(entity: EntityDecl<Extract<keyof EM, string>>, instanceId: string, newIndex: number): [
57
+ Transaction<EM, SEM>,
58
+ changedInstances: [id: string, oldContent: InstanceContent, newContent: InstanceContent][]
59
+ ];
55
60
  /**
56
61
  * @package
57
62
  */
@@ -1,8 +1,11 @@
1
+ import { isEmpty } from "@elyukai/utils/array/nonEmpty";
1
2
  import { randomUUID } from "node:crypto";
3
+ import { getSortOrderKeyPathForReorder } from "../shared/schema/utils/sortOrder.js";
4
+ import { getErrorMessageForDisplay, HTTPError } from "../shared/utils/error.js";
2
5
  import {} from "./schema/dsl/index.js";
3
6
  import {} from "./utils/databaseInMemory.js";
4
- import { getErrorMessageForDisplay, HTTPError } from "./utils/error.js";
5
7
  import { isReferencedByOtherInstances, updateReferencesToInstances, } from "./utils/references.js";
8
+ import { getSortOrderStartIndex } from "./utils/sortOrder.js";
6
9
  export const createNewId = () => randomUUID();
7
10
  /**
8
11
  * @throws {HTTPError}
@@ -208,6 +211,51 @@ export class Transaction {
208
211
  { content: oldInstance },
209
212
  ];
210
213
  }
214
+ reorderAllInstancesOfEntity(entity, newOrder) {
215
+ const { data, steps } = this.#values;
216
+ const positionKeyPath = getSortOrderKeyPathForReorder(entity);
217
+ const startIndex = getSortOrderStartIndex(entity);
218
+ const newData = data.reorderAllInstancesOfEntity(entity.name, positionKeyPath, newOrder, startIndex);
219
+ const newSteps = newData
220
+ .getAllInstanceContainersOfEntity(entity.name)
221
+ .map(instance => ({
222
+ kind: "update",
223
+ entity,
224
+ instanceId: instance.id,
225
+ instanceContent: instance.content,
226
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
227
+ oldInstance: data.getInstanceContainerOfEntityById(entity.name, instance.id).content,
228
+ }));
229
+ return new Transaction({
230
+ ...this.#values,
231
+ data: newData,
232
+ steps: [...steps, ...newSteps],
233
+ });
234
+ }
235
+ reorderInstanceOfEntity(entity, instanceId, newIndex) {
236
+ const { data, steps } = this.#values;
237
+ const startIndex = getSortOrderStartIndex(entity);
238
+ const positionKeyPath = getSortOrderKeyPathForReorder(entity);
239
+ const [newData, changedInstances] = data.reorderInstanceOfEntity(entity.name, positionKeyPath, instanceId, newIndex, startIndex);
240
+ if (isEmpty(changedInstances)) {
241
+ return [this, changedInstances];
242
+ }
243
+ const newSteps = changedInstances.map(([id, oldContent, newContent]) => ({
244
+ kind: "update",
245
+ entity,
246
+ instanceId: id,
247
+ instanceContent: newContent,
248
+ oldInstance: oldContent,
249
+ }));
250
+ return [
251
+ new Transaction({
252
+ ...this.#values,
253
+ data: newData,
254
+ steps: [...steps, ...newSteps],
255
+ }),
256
+ changedInstances,
257
+ ];
258
+ }
211
259
  /**
212
260
  * @package
213
261
  */
@@ -2,13 +2,13 @@ import { hasKey } from "@elyukai/utils/object";
2
2
  import { error, isError, ok } from "@elyukai/utils/result";
3
3
  import { createEnumCaseValue, ENUM_DISCRIMINATOR_KEY, } from "../../shared/schema/declarations/EnumDecl.js";
4
4
  import { NodeKind } from "../../shared/schema/Node.js";
5
+ import { HTTPError } from "../../shared/utils/error.js";
5
6
  import {} from "../schema/dsl/index.js";
6
7
  import { isChildEntitiesType } from "../schema/dsl/types/ChildEntitiesType.js";
7
8
  import { Case } from "../schema/generatedTypeHelpers.js";
8
9
  import { isEntityDeclWithParentReference, isEnumDecl, isIncludeIdentifierType, isReferenceIdentifierType, } from "../schema/guards.js";
9
10
  import { reduceNodes } from "../schema/helpers.js";
10
11
  import {} from "./databaseInMemory.js";
11
- import { HTTPError } from "./error.js";
12
12
  const isParentReferenceReferencingParent = (value, parentEntityName, parentId) => {
13
13
  if (typeof value === "object" && value !== null && hasKey(value, ENUM_DISCRIMINATOR_KEY)) {
14
14
  return (value[ENUM_DISCRIMINATOR_KEY] === parentEntityName &&
@@ -1,4 +1,5 @@
1
1
  import { Dictionary } from "@elyukai/utils/dictionary";
2
+ import { type KeyPath } from "../../shared/schema/utils/keyPath.ts";
2
3
  import type { InstanceContainer, InstanceContent, SingletonInstanceContainer } from "../../shared/utils/instances.ts";
3
4
  import type { EntityDecl, SingletonEntityDecl } from "../schema/dsl/index.ts";
4
5
  import { type AnyChildEntityMap, type AnyEntityMap, type AnySingletonEntityMap, type GetEntityByName, type IdArgsVariant, type RegisteredEntityMap, type RegisteredSingletonEntityMap } from "../schema/generatedTypeHelpers.ts";
@@ -21,6 +22,8 @@ export declare class DatabaseInMemory<EM extends AnyEntityMap = RegisteredEntity
21
22
  hasInstanceOfSingletonEntity(entityName: Extract<keyof SEM, string>): boolean;
22
23
  forEachInstance(fn: (entityName: Extract<keyof EM, string>, instance: InstanceContainer) => Promise<void>, async: true): Promise<void>;
23
24
  forEachInstance(fn: (entityName: Extract<keyof EM, string>, instance: InstanceContainer) => void, async?: false): void;
25
+ forEachInstanceOfEntity(entityName: Extract<keyof EM, string>, fn: (instance: InstanceContainer) => Promise<void>, async: true): Promise<void>;
26
+ forEachInstanceOfEntity(entityName: Extract<keyof EM, string>, fn: (instance: InstanceContainer) => void, async?: false): void;
24
27
  forEachSingletonInstance(fn: (entityName: Extract<keyof SEM, string>, instance: SingletonInstanceContainer) => Promise<void>, async: true): Promise<void>;
25
28
  forEachSingletonInstance(fn: (entityName: Extract<keyof SEM, string>, instance: SingletonInstanceContainer) => void, async?: false): void;
26
29
  countInstancesOfEntity(entityName: Extract<keyof EM, string>): number;
@@ -34,4 +37,9 @@ export declare class DatabaseInMemory<EM extends AnyEntityMap = RegisteredEntity
34
37
  map(fn: (instances: Dictionary<InstanceContainer>, entityName: Extract<keyof EM, string>) => Dictionary<InstanceContainer>): DatabaseInMemory<EM, SEM>;
35
38
  mapSingletons(fn: (instance: SingletonInstanceContainer, entityName: Extract<keyof SEM, string>) => SingletonInstanceContainer): DatabaseInMemory<EM, SEM>;
36
39
  getEntityNameOfInstanceId(id: string): Extract<keyof EM, string> | undefined;
40
+ reorderAllInstancesOfEntity(entityName: Extract<keyof EM, string>, keyPath: KeyPath, newOrder: string[], startIndex?: number): DatabaseInMemory<EM, SEM>;
41
+ reorderInstanceOfEntity(entityName: Extract<keyof EM, string>, keyPath: KeyPath, instanceId: string, newIndex: number, startIndex?: number): [
42
+ DatabaseInMemory<EM, SEM>,
43
+ changedInstances: [id: string, oldContent: InstanceContent, newContent: InstanceContent][]
44
+ ];
37
45
  }
@@ -1,12 +1,17 @@
1
+ import { reorder } from "@elyukai/utils/array/modify";
1
2
  import { mapAsync } from "@elyukai/utils/async";
2
3
  import { Dictionary } from "@elyukai/utils/dictionary";
3
4
  import { deepEqual } from "@elyukai/utils/equality";
5
+ import { on } from "@elyukai/utils/function";
4
6
  import { Lazy } from "@elyukai/utils/lazy";
7
+ import { compareNumber } from "@elyukai/utils/ordering";
5
8
  import child_process from "node:child_process";
6
9
  import { readdir, readFile, stat } from "node:fs/promises";
7
10
  import { basename, extname, join } from "node:path";
8
11
  import { platform } from "node:process";
9
12
  import { promisify } from "node:util";
13
+ import { getValueAtKeyPathIfDefined, setValueAtKeyPath, } from "../../shared/schema/utils/keyPath.js";
14
+ import { HTTPError } from "../../shared/utils/error.js";
10
15
  import { normalizedIdArgs, } from "../schema/generatedTypeHelpers.js";
11
16
  import { getPathToSingletonInstance, readSingletonInstance } from "./files.js";
12
17
  const exec = promisify(child_process.exec);
@@ -102,6 +107,15 @@ export class DatabaseInMemory {
102
107
  });
103
108
  }
104
109
  }
110
+ forEachInstanceOfEntity(entityName, ...args) {
111
+ const [fn, async] = args;
112
+ if (async) {
113
+ return this.#data.get(entityName)?.forEach(fn, true);
114
+ }
115
+ else {
116
+ this.#data.get(entityName)?.forEach(fn);
117
+ }
118
+ }
105
119
  forEachSingletonInstance(...args) {
106
120
  const [fn, async] = args;
107
121
  if (async) {
@@ -178,4 +192,49 @@ export class DatabaseInMemory {
178
192
  getEntityNameOfInstanceId(id) {
179
193
  return this.#data.findKey(instances => instances.has(id));
180
194
  }
195
+ reorderAllInstancesOfEntity(entityName, keyPath, newOrder, startIndex = 0) {
196
+ const instancesWithPositions = this.getAllInstanceContainersOfEntity(entityName).map(instance => [instance.id, getValueAtKeyPathIfDefined(instance.content, keyPath)]);
197
+ if (newOrder.length !== instancesWithPositions.length) {
198
+ throw new HTTPError(400, `New order of instances (${newOrder.length.toFixed()} elements) does not match the amount of instances in entity "${entityName}" (${instancesWithPositions.length.toFixed()} instances)`);
199
+ }
200
+ if (instancesWithPositions.some(([id]) => !newOrder.includes(id))) {
201
+ throw new HTTPError(400, `Identifiers in the new order do not match existing instance identifiers for entity "${entityName}"`);
202
+ }
203
+ return new DatabaseInMemory(instancesWithPositions.reduce((db, [id]) => db.modify(entityName, instances => instances?.modify(id, instanceContainer => instanceContainer === undefined
204
+ ? undefined
205
+ : {
206
+ ...instanceContainer,
207
+ content: setValueAtKeyPath(instanceContainer.content, keyPath, newOrder.indexOf(id) + startIndex),
208
+ })), this.#data), this.#singletonData);
209
+ }
210
+ reorderInstanceOfEntity(entityName, keyPath, instanceId, newIndex, startIndex = 0) {
211
+ const instancesWithPositions = this.getAllInstanceContainersOfEntity(entityName).map(instance => [instance.id, getValueAtKeyPathIfDefined(instance.content, keyPath)]);
212
+ const normalizedInstancesWithPositions = instancesWithPositions.toSorted(on(([_, position]) => position, compareNumber));
213
+ const currentIndex = normalizedInstancesWithPositions.findIndex(([id]) => id === instanceId);
214
+ if (currentIndex === -1) {
215
+ throw new HTTPError(400, `Instance with ID "${instanceId}" does not exist in entity "${entityName}"`);
216
+ }
217
+ const newSafeIndex = Math.max(0, Math.min(newIndex, normalizedInstancesWithPositions.length - 1));
218
+ if (currentIndex === newSafeIndex) {
219
+ return [this, []];
220
+ }
221
+ const reorderedInstances = reorder(normalizedInstancesWithPositions, currentIndex, newSafeIndex);
222
+ const changedInstances = [];
223
+ return [
224
+ new DatabaseInMemory(reorderedInstances.reduce((db, [id], index) => db.modify(entityName, instances => instances?.modify(id, instanceContainer => {
225
+ if (instanceContainer === undefined) {
226
+ return undefined;
227
+ }
228
+ const oldContent = instanceContainer.content;
229
+ const currentPosition = getValueAtKeyPathIfDefined(oldContent, keyPath);
230
+ if (currentPosition === index + startIndex) {
231
+ return instanceContainer;
232
+ }
233
+ const newContent = setValueAtKeyPath(oldContent, keyPath, index + startIndex);
234
+ changedInstances.push([id, oldContent, newContent]);
235
+ return { ...instanceContainer, content: newContent };
236
+ })), this.#data), this.#singletonData),
237
+ changedInstances,
238
+ ];
239
+ }
181
240
  }
@@ -1,10 +1,2 @@
1
1
  import type { Response } from "express";
2
- export declare const getErrorMessageForDisplay: (error: Error, indentation?: number) => string;
3
- export declare const countError: (error: Error) => number;
4
- export declare const countErrors: (errors: Error[]) => number;
5
- export declare const wrapErrorsIfAny: (message: string, errors: Error[]) => AggregateError | undefined;
6
- export declare class HTTPError extends Error {
7
- code: number;
8
- constructor(code: number, message: string);
9
- }
10
2
  export declare const sendErrorResponse: (res: Response, error: unknown) => void;
@@ -1,42 +1,4 @@
1
- import { applyIndentation } from "./render.js";
2
- import { UniqueConstraintError } from "./unique.js";
3
- export const getErrorMessageForDisplay = (error, indentation = 2) => {
4
- if (error instanceof AggregateError) {
5
- return `${error.message}\n${applyIndentation(1, error.errors
6
- .filter(subError => subError instanceof Error)
7
- .map(subError => getErrorMessageForDisplay(subError, indentation))
8
- .join("\n"), indentation)}`;
9
- }
10
- else if (error.cause instanceof Error) {
11
- return `${error.message}\n${applyIndentation(1, getErrorMessageForDisplay(error.cause, indentation), indentation)}`;
12
- }
13
- else if (error instanceof UniqueConstraintError) {
14
- return `${error.message}\n${applyIndentation(1, error.parts.join("\n"), indentation)}`;
15
- }
16
- else {
17
- return error.message;
18
- }
19
- };
20
- export const countError = (error) => error instanceof AggregateError
21
- ? countErrors(error.errors.filter(subError => subError instanceof Error))
22
- : error.cause instanceof Error
23
- ? countError(error.cause)
24
- : 1;
25
- export const countErrors = (errors) => errors.reduce((count, error) => count + countError(error), 0);
26
- export const wrapErrorsIfAny = (message, errors) => {
27
- if (errors.length === 0) {
28
- return undefined;
29
- }
30
- return new AggregateError(errors, message);
31
- };
32
- export class HTTPError extends Error {
33
- code;
34
- constructor(code, message) {
35
- super(message);
36
- this.code = code;
37
- this.name = "HTTPError";
38
- }
39
- }
1
+ import { getErrorMessageForDisplay, HTTPError } from "../../shared/utils/error.js";
40
2
  export const sendErrorResponse = (res, error) => {
41
3
  res.set("Content-Type", "text/plain");
42
4
  if (error instanceof HTTPError) {
@@ -0,0 +1,13 @@
1
+ import type { Lazy } from "@elyukai/utils/lazy";
2
+ import { type SortOrder } from "../../shared/schema/utils/sortOrder.ts";
3
+ import type { IntegerType, Type } from "../schema/dsl/index.ts";
4
+ export declare const getSortOrderType: (decl: {
5
+ name: string;
6
+ type: Lazy<Type>;
7
+ sortOrder?: SortOrder;
8
+ }) => IntegerType;
9
+ export declare const getSortOrderStartIndex: (decl: {
10
+ name: string;
11
+ type: Lazy<Type>;
12
+ sortOrder?: SortOrder;
13
+ }) => number;
@@ -0,0 +1,12 @@
1
+ import { getSortOrderKeyPathForReorder, } from "../../shared/schema/utils/sortOrder.js";
2
+ import { findTypeAtPath } from "../schema/helpers.js";
3
+ export const getSortOrderType = (decl) => {
4
+ const keyPath = getSortOrderKeyPathForReorder(decl);
5
+ return findTypeAtPath(decl.type.value, keyPath, { throwOnPathMismatch: true });
6
+ };
7
+ export const getSortOrderStartIndex = (decl) => {
8
+ const sortOrderType = getSortOrderType(decl);
9
+ return typeof sortOrderType.minimum === "number"
10
+ ? sortOrderType.minimum
11
+ : (sortOrderType.minimum?.value ?? 0);
12
+ };
@@ -2,10 +2,6 @@ import { type Result } from "@elyukai/utils/result";
2
2
  import type { InstanceContainer, InstanceContainerOverview } from "../../shared/utils/instances.ts";
3
3
  import type { EntityDecl } from "../schema/dsl/index.ts";
4
4
  import { type DatabaseInMemory } from "./databaseInMemory.ts";
5
- export declare class UniqueConstraintError extends Error {
6
- readonly parts: string[];
7
- constructor(message: string, parts: string[]);
8
- }
9
5
  /**
10
6
  * Checks all unique constraints for the provided entity and its instances.
11
7
  *
@@ -3,14 +3,8 @@ import { flatCombine } from "@elyukai/utils/array/transformations";
3
3
  import { deepEqual } from "@elyukai/utils/equality";
4
4
  import { error, isError, mapError, ok } from "@elyukai/utils/result";
5
5
  import { getValueAtKeyPath, getValueAtKeyPathIfDefined, renderKeyPath, } from "../../shared/schema/utils/keyPath.js";
6
+ import { UniqueConstraintError } from "../../shared/utils/unique.js";
6
7
  import {} from "./databaseInMemory.js";
7
- export class UniqueConstraintError extends Error {
8
- parts;
9
- constructor(message, parts) {
10
- super(message);
11
- this.parts = parts;
12
- }
13
- }
14
8
  const printUniqueConstraint = (constraint, values) => (Array.isArray(constraint) ? constraint : [constraint])
15
9
  .map((elem, i) => "keyPath" in elem
16
10
  ? renderKeyPath(elem.keyPath) +
@@ -58,6 +58,24 @@ export interface CreateInstanceOfEntityResponseBody {
58
58
  instance: InstanceContainer;
59
59
  isLocaleEntity: boolean;
60
60
  }
61
+ export interface ReorderAllInstancesOfEntityRequestBody {
62
+ /**
63
+ * All identifiers in the requested order.
64
+ */
65
+ order: string[];
66
+ }
67
+ export interface ReorderAllInstancesOfEntityResponseBody {
68
+ instances: InstanceContainerOverview[];
69
+ }
70
+ export interface ReorderInstanceOfEntityRequestBody {
71
+ /**
72
+ * The 0-based index of the instance in the new order.
73
+ */
74
+ targetIndex: number;
75
+ }
76
+ export interface ReorderInstanceOfEntityResponseBody {
77
+ instances: InstanceContainerOverview[];
78
+ }
61
79
  export interface GetInstanceOfEntityResponseBody {
62
80
  instance: InstanceContainer;
63
81
  isLocaleEntity: boolean;
@@ -15,3 +15,4 @@ export declare const renderParsedKeyPath: (keyPath: ParsedKeyPath) => string;
15
15
  export declare const getAtKeyPath: <T>(value: T, previousPath: ParsedKeyPath, remainingKeyPath: ParsedKeyPath, throwOnPathMismatch: boolean, fArray: (value: T, index: number) => Result<T, (previousPath: string) => string>, fObject: (value: T, key: string) => Result<T, (previousPath: string) => string>, ...fs: ((value: T, key: KeyPathElement) => Result<[T, skipKey?: boolean], void>)[]) => T;
16
16
  export declare const getValueAtKeyPath: (value: unknown, keyPath: KeyPath, throwOnPathMismatch?: boolean) => unknown;
17
17
  export declare const getValueAtKeyPathIfDefined: (value: unknown, keyPath: KeyPath) => unknown;
18
+ export declare const setValueAtKeyPath: (value: unknown, keyPath: KeyPath, newValue: unknown) => unknown;
@@ -64,3 +64,33 @@ export const getValueAtKeyPath = (value, keyPath, throwOnPathMismatch = false) =
64
64
  : error(previousPath => `Object at key path "${previousPath}" does not contain the key ${name}.`)
65
65
  : error(previousPath => `Key path "${previousPath}" does not contain an object.`));
66
66
  export const getValueAtKeyPathIfDefined = (value, keyPath) => trySafe(() => getValueAtKeyPath(value, keyPath, true));
67
+ const _setValueAtKeyPath = (value, keyPath, newValue) => {
68
+ if (isNotEmpty(keyPath)) {
69
+ const [key, ...remainingPath] = keyPath;
70
+ switch (key.kind) {
71
+ case "index":
72
+ if (!Array.isArray(value)) {
73
+ throw new TypeError(`Key path "${renderParsedKeyPath(keyPath)}" does not contain an array.`);
74
+ }
75
+ return [
76
+ ...value.slice(0, key.index),
77
+ _setValueAtKeyPath(value[key.index], remainingPath, newValue),
78
+ ...value.slice(key.index + 1),
79
+ ];
80
+ case "property":
81
+ if (typeof value !== "object" || value === null) {
82
+ throw new TypeError(`Key path "${renderParsedKeyPath(keyPath)}" does not contain an object.`);
83
+ }
84
+ return {
85
+ ...value,
86
+ [key.name]: _setValueAtKeyPath(value[key.name], remainingPath, newValue),
87
+ };
88
+ default:
89
+ return assertExhaustive(key);
90
+ }
91
+ }
92
+ else {
93
+ return newValue;
94
+ }
95
+ };
96
+ export const setValueAtKeyPath = (value, keyPath, newValue) => _setValueAtKeyPath(value, parseKeyPath(keyPath), newValue);
@@ -6,6 +6,7 @@ import { type KeyPath } from "./keyPath.ts";
6
6
  */
7
7
  export type SortOrder = "displayName" | {
8
8
  keyPath: KeyPath;
9
+ isIndex?: boolean;
9
10
  };
10
11
  /**
11
12
  * An instance with all values needed for sorting.
@@ -15,3 +16,11 @@ export type SortableInstance = [InstanceContainer, InstanceContainerOverview];
15
16
  * Sorts instances according to the sort order defined for the entity.
16
17
  */
17
18
  export declare const sortBySortOrder: <T extends SortableInstance>(instances: T[], sortOrder: SortOrder | undefined) => T[];
19
+ export declare const getSortOrderKeyPathForReorder: (decl: {
20
+ name: string;
21
+ sortOrder?: SortOrder;
22
+ }) => KeyPath;
23
+ export declare const isReorderableSortOrder: (sortOrder: SortOrder | undefined) => sortOrder is {
24
+ keyPath: KeyPath;
25
+ isIndex: true;
26
+ };
@@ -1,15 +1,10 @@
1
+ import { HTTPError } from "../../utils/error.js";
1
2
  import { getValueAtKeyPath } from "./keyPath.js";
2
- // | { entityMapKeyPath: KeyPath; keyPathInEntityMap: KeyPath }
3
3
  const getSortOrderValue = (instanceContent, sortOrder) => {
4
4
  if ("keyPath" in sortOrder) {
5
5
  return getValueAtKeyPath(instanceContent, sortOrder.keyPath);
6
6
  }
7
7
  else {
8
- // const entityMap = instance.getValueAtKeyPath(sortOrder.entityMapKeyPath)
9
- // if (entityMap && typeof entityMap === "object") {
10
- // const nestedInstance = InstanceContent.fromUnknown(entityMap)
11
- // return nestedInstance.getValueAtKeyPath(sortOrder.keyPathInEntityMap)
12
- // }
13
8
  return;
14
9
  }
15
10
  };
@@ -36,3 +31,13 @@ export const sortBySortOrder = (instances, sortOrder) => {
36
31
  .map(({ instance }) => instance);
37
32
  }
38
33
  };
34
+ export const getSortOrderKeyPathForReorder = (decl) => {
35
+ const { sortOrder } = decl;
36
+ if (isReorderableSortOrder(sortOrder)) {
37
+ return sortOrder.keyPath;
38
+ }
39
+ else {
40
+ throw new HTTPError(400, `Declaration "${decl.name}" does not have the sort order defined correctly. Please check the validity of the whole schema as any incorrect configuration should have been caught during schema intialization.`);
41
+ }
42
+ };
43
+ export const isReorderableSortOrder = (sortOrder) => typeof sortOrder === "object" && sortOrder.isIndex === true;
@@ -0,0 +1,8 @@
1
+ export declare const getErrorMessageForDisplay: (error: Error, indentation?: number) => string;
2
+ export declare const countError: (error: Error) => number;
3
+ export declare const countErrors: (errors: Error[]) => number;
4
+ export declare const wrapErrorsIfAny: (message: string, errors: Error[]) => AggregateError | undefined;
5
+ export declare class HTTPError extends Error {
6
+ code: number;
7
+ constructor(code: number, message: string);
8
+ }
@@ -0,0 +1,39 @@
1
+ import { applyIndentation } from "../../shared/utils/render.js";
2
+ import { UniqueConstraintError } from "./unique.js";
3
+ export const getErrorMessageForDisplay = (error, indentation = 2) => {
4
+ if (error instanceof AggregateError) {
5
+ return `${error.message}\n${applyIndentation(1, error.errors
6
+ .filter(subError => subError instanceof Error)
7
+ .map(subError => getErrorMessageForDisplay(subError, indentation))
8
+ .join("\n"), indentation)}`;
9
+ }
10
+ else if (error.cause instanceof Error) {
11
+ return `${error.message}\n${applyIndentation(1, getErrorMessageForDisplay(error.cause, indentation), indentation)}`;
12
+ }
13
+ else if (error instanceof UniqueConstraintError) {
14
+ return `${error.message}\n${applyIndentation(1, error.parts.join("\n"), indentation)}`;
15
+ }
16
+ else {
17
+ return error.message;
18
+ }
19
+ };
20
+ export const countError = (error) => error instanceof AggregateError
21
+ ? countErrors(error.errors.filter(subError => subError instanceof Error))
22
+ : error.cause instanceof Error
23
+ ? countError(error.cause)
24
+ : 1;
25
+ export const countErrors = (errors) => errors.reduce((count, error) => count + countError(error), 0);
26
+ export const wrapErrorsIfAny = (message, errors) => {
27
+ if (errors.length === 0) {
28
+ return undefined;
29
+ }
30
+ return new AggregateError(errors, message);
31
+ };
32
+ export class HTTPError extends Error {
33
+ code;
34
+ constructor(code, message) {
35
+ super(message);
36
+ this.code = code;
37
+ this.name = "HTTPError";
38
+ }
39
+ }
@@ -1,5 +1,5 @@
1
1
  import { mergeObjects } from "@elyukai/utils/object";
2
- import { EOL } from "node:os";
2
+ const EOL = await import("node:os").then(module => module.EOL).catch(() => "\n");
3
3
  export const prefixLines = (prefix, text, includeEmptyLines = false) => text
4
4
  .split(EOL)
5
5
  .map(line => (includeEmptyLines || line.length > 0 ? prefix + line : line))
@@ -0,0 +1,4 @@
1
+ export declare class UniqueConstraintError extends Error {
2
+ readonly parts: string[];
3
+ constructor(message: string, parts: string[]);
4
+ }
@@ -0,0 +1,7 @@
1
+ export class UniqueConstraintError extends Error {
2
+ parts;
3
+ constructor(message, parts) {
4
+ super(message);
5
+ this.parts = parts;
6
+ }
7
+ }
@@ -728,7 +728,7 @@ const request = async (method, url, options)=>{
728
728
  };
729
729
  const api_getResource = async (url, options)=>request("GET", url, options);
730
730
  const postResource = async (url, options)=>request("POST", url, options);
731
- const putResource = async (url, options)=>request("PUT", url, options);
731
+ const api_putResource = async (url, options)=>request("PUT", url, options);
732
732
  const deleteResource = async (url, options)=>request("DELETE", url, options);
733
733
  const getAllDeclarations = async (locales, kind)=>api_getResource("/api/declarations", {
734
734
  locales,
@@ -741,6 +741,15 @@ const getAllSingletonEntities = (locales)=>getAllDeclarations(locales, "Singleto
741
741
  const getInstancesByEntityName = async (locales, name)=>api_getResource(`/api/declarations/${name}/instances`, {
742
742
  locales
743
743
  });
744
+ const reorderAllInstancesInEntity = async (locales, name, newOrder)=>{
745
+ const body = {
746
+ order: newOrder
747
+ };
748
+ return api_putResource(`/api/declarations/${name}/instances/reorder`, {
749
+ locales,
750
+ body
751
+ });
752
+ };
744
753
  const getLocaleInstances = (locales, localeEntityName)=>localeEntityName ? getInstancesByEntityName(locales, localeEntityName) : Promise.resolve({
745
754
  instances: [],
746
755
  isLocaleEntity: true
@@ -764,7 +773,7 @@ const updateInstanceByEntityNameAndId = async (locales, name, id, content)=>{
764
773
  const body = {
765
774
  instance: content
766
775
  };
767
- return putResource(`/api/declarations/${name}/instances/${id}`, {
776
+ return api_putResource(`/api/declarations/${name}/instances/${id}`, {
768
777
  locales,
769
778
  body
770
779
  });
@@ -791,7 +800,7 @@ const updateSingletonInstanceByEntityName = async (locales, name, content)=>{
791
800
  const body = {
792
801
  instance: content
793
802
  };
794
- return putResource(`/api/declarations/${name}/instance`, {
803
+ return api_putResource(`/api/declarations/${name}/instance`, {
795
804
  locales,
796
805
  body
797
806
  });
@@ -2404,6 +2413,13 @@ const useMappedAPIResource = (apiFn, mapFn, ...args)=>{
2404
2413
  mapFn,
2405
2414
  ...args
2406
2415
  ]);
2416
+ const setNewData = hooks_module_q((result)=>{
2417
+ setData(mapFn(result));
2418
+ }, [
2419
+ apiFn,
2420
+ mapFn,
2421
+ ...args
2422
+ ]);
2407
2423
  hooks_module_h(()=>{
2408
2424
  fetchData().catch(logAndAlertError);
2409
2425
  }, [
@@ -2414,7 +2430,8 @@ const useMappedAPIResource = (apiFn, mapFn, ...args)=>{
2414
2430
  ]);
2415
2431
  return [
2416
2432
  data,
2417
- reload
2433
+ reload,
2434
+ setNewData
2418
2435
  ];
2419
2436
  };
2420
2437
  const letterOrDigit = /[a-zA-Z0-9]/;
@@ -5262,7 +5279,7 @@ const IntegerTypeInput = ({ type, value, disabled, onChange })=>{
5262
5279
  const numericValue = Number.parseInt(event.currentTarget.value, 10);
5263
5280
  if (!Number.isNaN(numericValue)) onChange(numericValue);
5264
5281
  },
5265
- step: 0.01,
5282
+ step: 1,
5266
5283
  "aria-invalid": errors.length > 0,
5267
5284
  disabled: disabled
5268
5285
  }),
@@ -6633,6 +6650,8 @@ const CreateSingletonInstance = ()=>/*#__PURE__*/ jsxRuntime_module_u(SingletonI
6633
6650
  onSubmit: CreateSingletonInstance_onSubmit,
6634
6651
  onSave: CreateSingletonInstance_onSave
6635
6652
  });
6653
+ await import("node:os").then((module)=>module.EOL).catch(()=>"\n");
6654
+ const isReorderableSortOrder = (sortOrder)=>"object" == typeof sortOrder && true === sortOrder.isIndex;
6636
6655
  const Entity_localeMapper = (result)=>result.instances;
6637
6656
  const mapInstances = (data)=>data.instances;
6638
6657
  const Entity = ()=>{
@@ -6644,8 +6663,34 @@ const Entity = ()=>{
6644
6663
  const gitClient = hooks_module_x(GitClientContext);
6645
6664
  const { reloadEntities } = hooks_module_x(EntitiesContext);
6646
6665
  const { declaration: entity, isLocaleEntity } = entityFromRoute ?? {};
6647
- const [instances, reloadInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, locales, entity?.name ?? name ?? "");
6666
+ const [instances, reloadInstances, setInstances] = useMappedAPIResource(getInstancesByEntityName, mapInstances, locales, entity?.name ?? name ?? "");
6648
6667
  const [localeInstances, reloadLocaleInstances] = useMappedAPIResource(getLocaleInstances, Entity_localeMapper, locales, config.localeEntityName);
6668
+ const [inEditMode, setInEditMode] = hooks_module_d(false);
6669
+ const [inApplyingNewOrder, setInApplyingNewOrder] = hooks_module_d(false);
6670
+ const [orderedInstances, setOrderedInstances] = hooks_module_d([]);
6671
+ hooks_module_h(()=>{
6672
+ setOrderedInstances(instances ?? []);
6673
+ }, [
6674
+ instances
6675
+ ]);
6676
+ const toggleEditMode = hooks_module_q(()=>{
6677
+ if (entity && !inApplyingNewOrder) if (inEditMode) {
6678
+ setInApplyingNewOrder(true);
6679
+ reorderAllInstancesInEntity(locales, entity.name, orderedInstances.map((i)=>i.id)).then((res)=>{
6680
+ setInstances(res);
6681
+ setInEditMode(false);
6682
+ }, logAndAlertError).finally(()=>{
6683
+ setInApplyingNewOrder(false);
6684
+ });
6685
+ } else setInEditMode(true);
6686
+ }, [
6687
+ entity,
6688
+ inApplyingNewOrder,
6689
+ inEditMode,
6690
+ locales,
6691
+ orderedInstances,
6692
+ setInstances
6693
+ ]);
6649
6694
  const { latestCommit } = hooks_module_x(GitClientContext) ?? {};
6650
6695
  hooks_module_h(()=>{
6651
6696
  reloadInstances().then(()=>reloadLocaleInstances()).catch(logAndAlertError);
@@ -6714,7 +6759,7 @@ const Entity = ()=>{
6714
6759
  name: "undefined" === key ? "No matching locale" : localeInstances?.find((instance)=>instance.id === key)?.displayName ?? key,
6715
6760
  instances: instancesByLocale[key] ?? []
6716
6761
  })).filter((group)=>group.instances.length > 0);
6717
- const instanceMapper = (instance)=>{
6762
+ const instanceMapper = (instance, index, instancesArr)=>{
6718
6763
  const gitStatusForDisplay = getGitStatusForDisplay(instance.gitStatus);
6719
6764
  return /*#__PURE__*/ jsxRuntime_module_u("li", {
6720
6765
  id: `instance-${instance.id}`,
@@ -6729,7 +6774,28 @@ const Entity = ()=>{
6729
6774
  class: "entries-item__subtitle entries-item__subtitle--id",
6730
6775
  children: instance.id
6731
6776
  }),
6732
- /*#__PURE__*/ jsxRuntime_module_u("div", {
6777
+ inEditMode ? /*#__PURE__*/ jsxRuntime_module_u("div", {
6778
+ class: "entries-item__side",
6779
+ children: /*#__PURE__*/ jsxRuntime_module_u("div", {
6780
+ class: "btns",
6781
+ children: [
6782
+ /*#__PURE__*/ jsxRuntime_module_u("button", {
6783
+ disabled: index <= 0,
6784
+ onClick: ()=>{
6785
+ setOrderedInstances((ins)=>reorder(ins, index, index - 1));
6786
+ },
6787
+ children: "Move up"
6788
+ }),
6789
+ /*#__PURE__*/ jsxRuntime_module_u("button", {
6790
+ disabled: index >= instancesArr.length - 1,
6791
+ onClick: ()=>{
6792
+ setOrderedInstances((ins)=>reorder(ins, index, index + 1));
6793
+ },
6794
+ children: "Move down"
6795
+ })
6796
+ ]
6797
+ })
6798
+ }) : /*#__PURE__*/ jsxRuntime_module_u("div", {
6733
6799
  class: "entries-item__side",
6734
6800
  children: [
6735
6801
  /*#__PURE__*/ jsxRuntime_module_u(GitStatusIndicator, {
@@ -6773,10 +6839,19 @@ const Entity = ()=>{
6773
6839
  /*#__PURE__*/ jsxRuntime_module_u("h1", {
6774
6840
  children: toTitleCase(entity.namePlural)
6775
6841
  }),
6776
- /*#__PURE__*/ jsxRuntime_module_u("a", {
6777
- class: "btn btn--primary",
6778
- href: `/entities/${entity.name}/instances/create`,
6779
- children: "Add"
6842
+ /*#__PURE__*/ jsxRuntime_module_u("div", {
6843
+ className: "btns",
6844
+ children: [
6845
+ isReorderableSortOrder(entity.sortOrder) ? /*#__PURE__*/ jsxRuntime_module_u("button", {
6846
+ onClick: toggleEditMode,
6847
+ children: inEditMode ? "Apply new order" : "Reorder"
6848
+ }) : null,
6849
+ /*#__PURE__*/ jsxRuntime_module_u("a", {
6850
+ class: "btn btn--primary",
6851
+ href: `/entities/${entity.name}/instances/create`,
6852
+ children: "Add"
6853
+ })
6854
+ ]
6780
6855
  })
6781
6856
  ]
6782
6857
  }),
@@ -6814,13 +6889,17 @@ const Entity = ()=>{
6814
6889
  value: searchText,
6815
6890
  onInput: (event)=>{
6816
6891
  setSearchText(event.currentTarget.value);
6817
- }
6892
+ },
6893
+ disabled: inEditMode
6818
6894
  })
6819
6895
  ]
6820
6896
  })
6821
6897
  ]
6822
6898
  }),
6823
- isLocaleEntity || 1 === groupedInstances.length && groupedInstances[0]?.id !== "undefined" && locales[0] === groupedInstances[0]?.id ? /*#__PURE__*/ jsxRuntime_module_u("ul", {
6899
+ inEditMode ? /*#__PURE__*/ jsxRuntime_module_u("ul", {
6900
+ class: "entries entries--instances",
6901
+ children: orderedInstances.map(instanceMapper)
6902
+ }) : isLocaleEntity || 1 === groupedInstances.length && groupedInstances[0]?.id !== "undefined" && locales[0] === groupedInstances[0]?.id ? /*#__PURE__*/ jsxRuntime_module_u("ul", {
6824
6903
  class: "entries entries--instances",
6825
6904
  children: filteredInstances.map(instanceMapper)
6826
6905
  }) : /*#__PURE__*/ jsxRuntime_module_u("ul", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsondb",
3
- "version": "0.21.2",
3
+ "version": "0.21.4",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "Lukas Obermann",
@@ -487,8 +487,9 @@ select {
487
487
  align-items: start;
488
488
  }
489
489
 
490
- .header-with-btns button,
491
- .header-with-btns .btn {
490
+ .header-with-btns > .btns,
491
+ .header-with-btns > button,
492
+ .header-with-btns > .btn {
492
493
  margin-top: 2.3rem;
493
494
  }
494
495
 
File without changes