@myko/ui-svelte 4.4.0 → 4.4.3

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 (80) hide show
  1. package/{src/lib → dist}/components/ConnectionStats.svelte +8 -22
  2. package/dist/components/ConnectionStats.svelte.d.ts +10 -0
  3. package/dist/components/EntityDiffBadge.svelte +10 -0
  4. package/dist/components/EntityDiffBadge.svelte.d.ts +7 -0
  5. package/{src/lib → dist}/components/EntityDiffFields.svelte +27 -49
  6. package/dist/components/EntityDiffFields.svelte.d.ts +11 -0
  7. package/{src/lib → dist}/components/EntityDiffNode.svelte +25 -43
  8. package/dist/components/EntityDiffNode.svelte.d.ts +14 -0
  9. package/{src/lib → dist}/components/EntityDiffTree.svelte +150 -200
  10. package/dist/components/EntityDiffTree.svelte.d.ts +21 -0
  11. package/dist/components/Logs.svelte +30 -0
  12. package/dist/components/Logs.svelte.d.ts +7 -0
  13. package/dist/components/Query.svelte +21 -0
  14. package/dist/components/Query.svelte.d.ts +35 -0
  15. package/dist/components/Report.svelte +13 -0
  16. package/dist/components/Report.svelte.d.ts +32 -0
  17. package/dist/components/Search.svelte +46 -0
  18. package/dist/components/Search.svelte.d.ts +56 -0
  19. package/dist/components/ServerView.svelte +76 -0
  20. package/dist/components/ServerView.svelte.d.ts +8 -0
  21. package/dist/components/state/resolutions.d.ts +11 -0
  22. package/dist/components/state/resolutions.js +129 -0
  23. package/dist/components/state/viewstate.svelte.d.ts +60 -0
  24. package/dist/components/state/viewstate.svelte.js +259 -0
  25. package/dist/components/state/windback.svelte.d.ts +16 -0
  26. package/dist/components/state/windback.svelte.js +65 -0
  27. package/dist/components/transactions/EntityHistory.svelte +135 -0
  28. package/dist/components/transactions/EntityHistory.svelte.d.ts +10 -0
  29. package/{src/lib → dist}/components/transactions/TimeStrip.svelte +18 -25
  30. package/dist/components/transactions/TimeStrip.svelte.d.ts +3 -0
  31. package/dist/components/transactions/TransactionDetails.svelte +24 -0
  32. package/dist/components/transactions/TransactionDetails.svelte.d.ts +7 -0
  33. package/{src/lib → dist}/components/transactions/TransactionEvent.svelte +20 -32
  34. package/dist/components/transactions/TransactionEvent.svelte.d.ts +7 -0
  35. package/{src/lib → dist}/components/transactions/TransactionEventGroup.svelte +8 -13
  36. package/dist/components/transactions/TransactionEventGroup.svelte.d.ts +8 -0
  37. package/dist/components/transactions/Transactions.svelte +94 -0
  38. package/dist/components/transactions/Transactions.svelte.d.ts +8 -0
  39. package/{src/lib → dist}/components/transactions/TransactonView.svelte +3 -7
  40. package/dist/components/transactions/TransactonView.svelte.d.ts +7 -0
  41. package/{src/lib → dist}/components/windback/WindbackFrame.svelte +3 -6
  42. package/dist/components/windback/WindbackFrame.svelte.d.ts +7 -0
  43. package/dist/components/windback/index.js +1 -0
  44. package/dist/index.d.ts +18 -0
  45. package/{src/lib/index.ts → dist/index.js} +2 -15
  46. package/dist/services/svelte-client.svelte.d.ts +278 -0
  47. package/dist/services/svelte-client.svelte.js +678 -0
  48. package/dist/utils/entity-apply.d.ts +17 -0
  49. package/dist/utils/entity-apply.js +35 -0
  50. package/dist/utils/entity-diff.d.ts +40 -0
  51. package/dist/utils/entity-diff.js +57 -0
  52. package/dist/utils/entity-tree.d.ts +24 -0
  53. package/dist/utils/entity-tree.js +111 -0
  54. package/package.json +13 -9
  55. package/.prettierignore +0 -4
  56. package/.prettierrc +0 -15
  57. package/src/app.d.ts +0 -13
  58. package/src/app.html +0 -12
  59. package/src/lib/components/EntityDiffBadge.svelte +0 -18
  60. package/src/lib/components/Logs.svelte +0 -37
  61. package/src/lib/components/Query.svelte +0 -34
  62. package/src/lib/components/Report.svelte +0 -25
  63. package/src/lib/components/Search.svelte +0 -85
  64. package/src/lib/components/ServerView.svelte +0 -95
  65. package/src/lib/components/state/resolutions.ts +0 -137
  66. package/src/lib/components/state/viewstate.svelte.ts +0 -375
  67. package/src/lib/components/state/windback.svelte.ts +0 -88
  68. package/src/lib/components/transactions/EntityHistory.svelte +0 -173
  69. package/src/lib/components/transactions/TransactionDetails.svelte +0 -26
  70. package/src/lib/components/transactions/Transactions.svelte +0 -111
  71. package/src/lib/services/svelte-client.svelte.ts +0 -863
  72. package/src/lib/utils/entity-apply.ts +0 -47
  73. package/src/lib/utils/entity-diff.ts +0 -105
  74. package/src/lib/utils/entity-tree.ts +0 -130
  75. package/src/routes/+page.svelte +0 -3
  76. package/static/favicon.png +0 -0
  77. package/svelte.config.js +0 -18
  78. package/tsconfig.json +0 -13
  79. package/vite.config.ts +0 -6
  80. /package/{src/lib/components/windback/index.ts → dist/components/windback/index.d.ts} +0 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Compute the minimal SET/DEL operations to apply an import.
3
+ *
4
+ * @param diffs - Flat diff map from diffEntityLists()
5
+ * @param allFkFields - All FK field names used in BelongsTo relationships (for cascade detection)
6
+ * @param excluded - Optional set of "Type:id" keys to skip (user chose to keep current version)
7
+ */
8
+ export function computeApplyPlan(diffs, allFkFields, excluded) {
9
+ const toSet = [];
10
+ const allRemoved = new Map();
11
+ for (const [key, diff] of diffs) {
12
+ if (excluded?.has(key))
13
+ continue;
14
+ if (diff.status === 'added' || diff.status === 'modified') {
15
+ toSet.push(diff.entity);
16
+ }
17
+ else if (diff.status === 'removed') {
18
+ allRemoved.set(diff.entity.data.id, diff.entity);
19
+ }
20
+ }
21
+ // Filter removed to shallowest only — if a removed entity's parent is also
22
+ // removed, skip it (the cascade will handle it)
23
+ const removedIds = new Set(allRemoved.keys());
24
+ const toDel = [];
25
+ for (const [_id, entity] of allRemoved) {
26
+ const hasRemovedParent = allFkFields.some((fk) => {
27
+ const parentId = entity.data[fk];
28
+ return parentId && removedIds.has(parentId);
29
+ });
30
+ if (!hasRemovedParent) {
31
+ toDel.push(entity);
32
+ }
33
+ }
34
+ return { toSet, toDel };
35
+ }
@@ -0,0 +1,40 @@
1
+ /** Diff status for an entity or field. */
2
+ export type DiffStatus = 'added' | 'removed' | 'modified' | 'unchanged';
3
+ /** Field-level diff for a modified entity. */
4
+ export interface FieldDiff {
5
+ field: string;
6
+ oldValue?: unknown;
7
+ newValue?: unknown;
8
+ }
9
+ /** Entity-level diff node with children for tree display. */
10
+ export interface EntityDiff {
11
+ type: string;
12
+ id: string;
13
+ status: DiffStatus;
14
+ /** Display label (entity name or id). */
15
+ name?: string;
16
+ /** Field-level changes — only populated for 'modified' status. */
17
+ fields?: FieldDiff[];
18
+ /** The current (server) entity data — only populated for 'modified' status. */
19
+ currentData?: Record<string, unknown>;
20
+ /** The incoming entity data — only populated for 'modified' status. */
21
+ incomingData?: Record<string, unknown>;
22
+ children: EntityDiff[];
23
+ }
24
+ /** A single exported entity from an EntityTreeExport. */
25
+ export interface ExportedEntity {
26
+ entityType: string;
27
+ data: Record<string, unknown>;
28
+ }
29
+ /**
30
+ * Compare two flat entity lists and produce field-level diffs.
31
+ *
32
+ * @param current - Entities currently on the server
33
+ * @param incoming - Entities from the import source (file or snapshot)
34
+ * @returns Map of "type:id" -> { status, fields }
35
+ */
36
+ export declare function diffEntityLists(current: ExportedEntity[], incoming: ExportedEntity[]): Map<string, {
37
+ status: DiffStatus;
38
+ fields?: FieldDiff[];
39
+ entity: ExportedEntity;
40
+ }>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Compare two flat entity lists and produce field-level diffs.
3
+ *
4
+ * @param current - Entities currently on the server
5
+ * @param incoming - Entities from the import source (file or snapshot)
6
+ * @returns Map of "type:id" -> { status, fields }
7
+ */
8
+ export function diffEntityLists(current, incoming) {
9
+ const currentMap = new Map();
10
+ for (const e of current) {
11
+ currentMap.set(`${e.entityType}:${e.data.id}`, e);
12
+ }
13
+ const incomingMap = new Map();
14
+ for (const e of incoming) {
15
+ incomingMap.set(`${e.entityType}:${e.data.id}`, e);
16
+ }
17
+ const result = new Map();
18
+ // Check incoming entities against current
19
+ for (const [key, inc] of incomingMap) {
20
+ const cur = currentMap.get(key);
21
+ if (!cur) {
22
+ result.set(key, { status: 'added', entity: inc });
23
+ }
24
+ else {
25
+ const fields = diffFields(cur.data, inc.data);
26
+ if (fields.length > 0) {
27
+ result.set(key, { status: 'modified', fields, entity: inc, currentEntity: cur });
28
+ }
29
+ else {
30
+ result.set(key, { status: 'unchanged', entity: inc });
31
+ }
32
+ }
33
+ }
34
+ // Check for removed entities (in current but not in incoming)
35
+ for (const [key, cur] of currentMap) {
36
+ if (!incomingMap.has(key)) {
37
+ result.set(key, { status: 'removed', entity: cur });
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ /** Compare two entity data objects field-by-field. */
43
+ function diffFields(oldData, newData) {
44
+ const diffs = [];
45
+ const allKeys = new Set([...Object.keys(oldData), ...Object.keys(newData)]);
46
+ for (const key of allKeys) {
47
+ // Skip hash — it's derived and always changes when fields change
48
+ if (key === 'hash')
49
+ continue;
50
+ const oldVal = oldData[key];
51
+ const newVal = newData[key];
52
+ if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
53
+ diffs.push({ field: key, oldValue: oldVal, newValue: newVal });
54
+ }
55
+ }
56
+ return diffs;
57
+ }
@@ -0,0 +1,24 @@
1
+ import type { EntityDiff, ExportedEntity, DiffStatus, FieldDiff } from './entity-diff.js';
2
+ /**
3
+ * Maps child entity type to its parent FK field names (camelCase).
4
+ * Provided by the consuming application — generated from Rust relationship registrations.
5
+ */
6
+ export type ParentFkFields = Record<string, string[]>;
7
+ /**
8
+ * Build an EntityDiff tree from a flat diff map.
9
+ *
10
+ * Entities with known parent FK fields are placed under their parent.
11
+ * Entities without a parent mapping (or whose parent isn't in the diff)
12
+ * are placed as direct children of the root so they remain visible.
13
+ *
14
+ * @param rootType - The root entity type
15
+ * @param rootId - The root entity ID
16
+ * @param diffs - Flat diff map from diffEntityLists()
17
+ * @returns Root EntityDiff node with nested children
18
+ */
19
+ export declare function buildDiffTree(rootType: string, rootId: string, diffs: Map<string, {
20
+ status: DiffStatus;
21
+ fields?: FieldDiff[];
22
+ entity: ExportedEntity;
23
+ currentEntity?: ExportedEntity;
24
+ }>, parentFkFields: ParentFkFields): EntityDiff;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Extract a display name from entity data.
3
+ * Tries 'name' field first, falls back to 'id'.
4
+ */
5
+ function entityName(data) {
6
+ return data.name ?? data.id ?? 'unknown';
7
+ }
8
+ /**
9
+ * Build an EntityDiff tree from a flat diff map.
10
+ *
11
+ * Entities with known parent FK fields are placed under their parent.
12
+ * Entities without a parent mapping (or whose parent isn't in the diff)
13
+ * are placed as direct children of the root so they remain visible.
14
+ *
15
+ * @param rootType - The root entity type
16
+ * @param rootId - The root entity ID
17
+ * @param diffs - Flat diff map from diffEntityLists()
18
+ * @returns Root EntityDiff node with nested children
19
+ */
20
+ export function buildDiffTree(rootType, rootId, diffs, parentFkFields) {
21
+ // Build flat EntityDiff nodes and index by ID for parent lookup
22
+ const allNodes = new Map();
23
+ const idToKey = new Map();
24
+ const childrenByParent = new Map();
25
+ const placed = new Set();
26
+ for (const [key, diff] of diffs) {
27
+ const [type, id] = key.split(':');
28
+ const node = {
29
+ type,
30
+ id,
31
+ status: diff.status,
32
+ name: entityName(diff.entity.data),
33
+ fields: diff.fields,
34
+ currentData: diff.currentEntity?.data ?? (diff.status === 'removed' ? diff.entity.data : undefined),
35
+ incomingData: diff.status === 'added' || diff.status === 'modified' ? diff.entity.data : undefined,
36
+ children: [],
37
+ };
38
+ allNodes.set(key, node);
39
+ idToKey.set(id, key);
40
+ }
41
+ // Build parent-child relationships
42
+ for (const [key, diff] of diffs) {
43
+ const [type] = key.split(':');
44
+ const fkFields = parentFkFields[type];
45
+ if (fkFields) {
46
+ let foundParent = false;
47
+ for (const fkField of fkFields) {
48
+ const parentId = diff.entity.data[fkField];
49
+ if (parentId) {
50
+ const parentKey = idToKey.get(parentId);
51
+ if (parentKey) {
52
+ if (!childrenByParent.has(parentKey)) {
53
+ childrenByParent.set(parentKey, []);
54
+ }
55
+ childrenByParent.get(parentKey).push(allNodes.get(key));
56
+ placed.add(key);
57
+ foundParent = true;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ // Get or create root node
65
+ const rootKey = `${rootType}:${rootId}`;
66
+ const rootNode = allNodes.get(rootKey) ?? {
67
+ type: rootType,
68
+ id: rootId,
69
+ status: 'unchanged',
70
+ name: rootId,
71
+ children: [],
72
+ };
73
+ placed.add(rootKey);
74
+ // Attach children from FK relationships
75
+ for (const [key, node] of allNodes) {
76
+ node.children = childrenByParent.get(key) ?? [];
77
+ }
78
+ // Orphaned entities (not the root, not placed under any parent) become root children
79
+ for (const [key, node] of allNodes) {
80
+ if (!placed.has(key)) {
81
+ rootNode.children.push(node);
82
+ }
83
+ }
84
+ // Deduplicate all children arrays — entities reachable via multiple FK paths
85
+ // (e.g., BindingNodeConnection with both start_node_id and end_node_id) could
86
+ // appear more than once
87
+ for (const node of allNodes.values()) {
88
+ if (node.children.length > 0) {
89
+ const seen = new Set();
90
+ node.children = node.children.filter((c) => {
91
+ const k = `${c.type}:${c.id}`;
92
+ if (seen.has(k))
93
+ return false;
94
+ seen.add(k);
95
+ return true;
96
+ });
97
+ }
98
+ }
99
+ // Also dedup root if it was created fresh (not from allNodes)
100
+ if (rootNode.children.length > 0) {
101
+ const seen = new Set();
102
+ rootNode.children = rootNode.children.filter((c) => {
103
+ const k = `${c.type}:${c.id}`;
104
+ if (seen.has(k))
105
+ return false;
106
+ seen.add(k);
107
+ return true;
108
+ });
109
+ }
110
+ return rootNode;
111
+ }
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "dependencies": {
3
- "@myko/core": "^4.4.0"
3
+ "@myko/core": "^4.4.3",
4
+ "luxon": "^3.5.0",
5
+ "ramda": "^0.30.1",
6
+ "rxjs": "^7.8.1",
7
+ "svelte-inview": "^4.0.4",
8
+ "svelte-watch-resize": "^1.0.3"
4
9
  },
5
10
  "devDependencies": {
6
11
  "@sveltejs/adapter-auto": "^3.0.0",
@@ -10,25 +15,24 @@
10
15
  "@types/luxon": "^3.4.2",
11
16
  "@types/ramda": "^0.30.2",
12
17
  "chokidar-cli": "^3.0.0",
13
- "luxon": "^3.5.0",
14
18
  "prettier": "^3.3.2",
15
19
  "prettier-plugin-svelte": "^3.2.6",
16
20
  "publint": "^0.2.0",
17
- "ramda": "^0.30.1",
18
- "rxjs": "^7.8.1",
19
21
  "svelte": "^5.17.5",
20
22
  "svelte-check": "^4.0.0",
21
- "svelte-inview": "^4.0.4",
22
- "svelte-watch-resize": "^1.0.3",
23
23
  "typescript": "^5.0.0",
24
24
  "vite": "^5.0.11"
25
25
  },
26
26
  "exports": {
27
27
  ".": {
28
- "default": "./src/lib/index.ts",
29
- "svelte": "./src/lib/index.ts"
28
+ "default": "./dist/index.js",
29
+ "svelte": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
30
31
  }
31
32
  },
33
+ "files": [
34
+ "dist"
35
+ ],
32
36
  "flux": {
33
37
  "tasks": [
34
38
  "build",
@@ -60,5 +64,5 @@
60
64
  "**/*.css"
61
65
  ],
62
66
  "type": "module",
63
- "version": "4.4.0"
67
+ "version": "4.4.3"
64
68
  }
package/.prettierignore DELETED
@@ -1,4 +0,0 @@
1
- # Package Managers
2
- package-lock.json
3
- pnpm-lock.yaml
4
- yarn.lock
package/.prettierrc DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "useTabs": true,
3
- "singleQuote": true,
4
- "trailingComma": "none",
5
- "printWidth": 100,
6
- "plugins": ["prettier-plugin-svelte"],
7
- "overrides": [
8
- {
9
- "files": "*.svelte",
10
- "options": {
11
- "parser": "svelte"
12
- }
13
- }
14
- ]
15
- }
package/src/app.d.ts DELETED
@@ -1,13 +0,0 @@
1
- // See https://svelte.dev/docs/kit/types#app.d.ts
2
- // for information about these interfaces
3
- declare global {
4
- namespace App {
5
- // interface Error {}
6
- // interface Locals {}
7
- // interface PageData {}
8
- // interface PageState {}
9
- // interface Platform {}
10
- }
11
- }
12
-
13
- export {};
package/src/app.html DELETED
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <link rel="icon" href="%sveltekit.assets%/favicon.png" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- %sveltekit.head%
8
- </head>
9
- <body data-sveltekit-preload-data="hover">
10
- <div>%sveltekit.body%</div>
11
- </body>
12
- </html>
@@ -1,18 +0,0 @@
1
- <script lang="ts">
2
- import type { DiffStatus } from '../utils/entity-diff.js';
3
-
4
- interface Props {
5
- status: DiffStatus;
6
- }
7
-
8
- let { status }: Props = $props();
9
-
10
- const config: Record<DiffStatus, { label: string; class: string }> = {
11
- added: { label: 'Added', class: 'badge-success' },
12
- removed: { label: 'Removed', class: 'badge-error' },
13
- modified: { label: 'Modified', class: 'badge-warning' },
14
- unchanged: { label: 'Unchanged', class: 'badge-ghost' },
15
- };
16
- </script>
17
-
18
- <span class="badge badge-sm {config[status].class}">{config[status].label}</span>
@@ -1,37 +0,0 @@
1
- <script lang="ts">
2
- import { startWith } from 'rxjs';
3
- import { myko as client } from '../services/svelte-client.svelte.js';
4
- import { Loggers, type ID } from '@myko/core';
5
-
6
- type Props = {
7
- serverId: ID;
8
- };
9
-
10
- const { serverId }: Props = $props();
11
-
12
- const logs = client.watchReport(new Loggers({})).pipe(startWith([]));
13
- </script>
14
-
15
- {#each $logs as log}
16
- <div class="log">
17
- <input type="checkbox" />
18
- <span>
19
- {log}
20
- </span>
21
- </div>
22
- {/each}
23
-
24
- <style>
25
- .log {
26
- display: flex;
27
- align-items: center;
28
- }
29
-
30
- .log input {
31
- margin-right: 0.5rem;
32
- }
33
-
34
- .log span {
35
- flex: 1;
36
- }
37
- </style>
@@ -1,34 +0,0 @@
1
- <script lang="ts" generics="Q extends Query<unknown>">
2
- import type { Query, QueryItem } from '@myko/core';
3
- import { getMykoClient, type SvelteMykoClient } from '../services/svelte-client.svelte.js';
4
- import type { Snippet } from 'svelte';
5
-
6
- interface Props {
7
- query: Q;
8
- client?: SvelteMykoClient;
9
- children: Snippet<[QueryItem<Q> & { id: string }]>;
10
- empty?: Snippet;
11
- loading?: Snippet;
12
- }
13
-
14
- let { query, client, children, empty, loading }: Props = $props();
15
-
16
- const resolvedClient = client ?? getMykoClient();
17
- const result = resolvedClient.liveQuery(() => query);
18
- </script>
19
-
20
- {#if result.items.size === 0}
21
- {#if !result.resolved}
22
- {#if loading}
23
- {@render loading()}
24
- {:else}
25
- <span class="query-loading">Loading...</span>
26
- {/if}
27
- {:else if empty}
28
- {@render empty()}
29
- {/if}
30
- {:else}
31
- {#each result.items as [id, item] (id)}
32
- {@render children(item)}
33
- {/each}
34
- {/if}
@@ -1,25 +0,0 @@
1
- <script lang="ts" generics="R extends Report<unknown>">
2
- import type { Report, ReportResult } from '@myko/core';
3
- import { getMykoClient, type SvelteMykoClient } from '../services/svelte-client.svelte.js';
4
- import type { Snippet } from 'svelte';
5
-
6
- interface Props {
7
- report: R;
8
- client?: SvelteMykoClient;
9
- children: Snippet<[ReportResult<R>]>;
10
- loading?: Snippet;
11
- }
12
-
13
- let { report, client, children, loading }: Props = $props();
14
-
15
- const resolvedClient = client ?? getMykoClient();
16
- const result = resolvedClient.liveReport(() => report);
17
- </script>
18
-
19
- {#if result.value === undefined}
20
- {#if loading}
21
- {@render loading()}
22
- {/if}
23
- {:else}
24
- {@render children(result.value)}
25
- {/if}
@@ -1,85 +0,0 @@
1
- <script lang="ts" generics="T extends { id: string }">
2
- import { EntitySearch, type Query } from '@myko/core';
3
- import { getMykoClient, type SvelteMykoClient } from '../services/svelte-client.svelte.js';
4
- import type { Snippet } from 'svelte';
5
-
6
- interface Props {
7
- /** Entity type to search (e.g., "Target", "Scene") */
8
- entityType: string;
9
- /** Search query string */
10
- query: string;
11
- /** Maximum number of results (default: 100) */
12
- limit?: number;
13
- /** Query factory to fetch items by IDs - receives array of matching IDs */
14
- queryByIds: (ids: string[]) => Query<T>;
15
- /** Query to use when search query is empty (shows all items) */
16
- showAllOnEmpty?: Query<T>;
17
- /** Optional client instance */
18
- client?: SvelteMykoClient;
19
- /** Render snippet for each result item */
20
- children: Snippet<[T]>;
21
- /** Render snippet when no results found */
22
- empty?: Snippet;
23
- /** Render snippet while loading */
24
- loading?: Snippet;
25
- }
26
-
27
- let {
28
- entityType,
29
- query,
30
- limit = 100,
31
- queryByIds,
32
- showAllOnEmpty,
33
- client,
34
- children,
35
- empty,
36
- loading
37
- }: Props = $props();
38
-
39
- const resolvedClient = client ?? getMykoClient();
40
-
41
- // Subscribe to EntitySearch report when we have a query
42
- const searchResult = resolvedClient.liveReport(() =>
43
- query?.trim() ? new EntitySearch({ entityType, query, limit }) : null,
44
- );
45
-
46
- // Get the matching IDs from search result
47
- const searchIds = $derived(searchResult.value?.ids ?? []);
48
-
49
- // Subscribe to items - either by search IDs, showAllOnEmpty, or nothing
50
- const itemsResult = resolvedClient.liveQuery(() => {
51
- if (query?.trim()) {
52
- // Have a search query - fetch by search result IDs
53
- return searchIds.length > 0 ? queryByIds(searchIds) : null;
54
- } else if (showAllOnEmpty) {
55
- // Empty query with showAllOnEmpty - fetch all
56
- return showAllOnEmpty;
57
- }
58
- return null;
59
- });
60
-
61
- // Derived loading state
62
- const isLoading = $derived(
63
- query?.trim()
64
- ? !searchResult.value || (searchIds.length > 0 && !itemsResult.resolved)
65
- : showAllOnEmpty
66
- ? !itemsResult.resolved
67
- : false,
68
- );
69
- </script>
70
-
71
- {#if isLoading}
72
- {#if loading}
73
- {@render loading()}
74
- {:else}
75
- <span class="search-loading">Searching...</span>
76
- {/if}
77
- {:else if itemsResult.items.size === 0}
78
- {#if empty}
79
- {@render empty()}
80
- {/if}
81
- {:else}
82
- {#each itemsResult.items as [id, item] (id)}
83
- {@render children(item)}
84
- {/each}
85
- {/if}
@@ -1,95 +0,0 @@
1
- <script lang="ts">
2
- import {
3
- LOG_RANK,
4
- type LogLevel,
5
- PeerAlive,
6
- ServerLogLevel,
7
- SetLogLevel,
8
- type Server
9
- } from '@myko/core';
10
- import { DateTime } from 'luxon';
11
- import { Observable, interval, switchMap } from 'rxjs';
12
- import { onDestroy } from 'svelte';
13
- import { myko as client } from '../services/svelte-client.svelte.js';
14
-
15
- interface Props {
16
- server: Server;
17
- isClientServer?: boolean;
18
- }
19
-
20
- let { server, isClientServer = false }: Props = $props();
21
-
22
- let alive = $derived(
23
- isClientServer
24
- ? (interval(500).pipe(switchMap(() => client.ping().catch((e) => false))) as Observable<
25
- number | false
26
- >)
27
- : client.watchReport(new PeerAlive({ peerId: server.id }))
28
- );
29
-
30
- let ping = $derived.by(() => {
31
- const value = $alive;
32
- if (value === undefined) return 'Connecting';
33
- if (value === false) return 'Dead';
34
- if (!Number.isFinite(value) || value < 0) return 'Unknown';
35
- return `${Math.round(value)}ms`;
36
- });
37
-
38
- let started = $state(DateTime.fromISO(server.startedAt).toRelative());
39
-
40
- let startedInterbal = setInterval(() => {
41
- started = DateTime.fromISO(server.startedAt).toRelative();
42
- }, 1000);
43
-
44
- onDestroy(() => {
45
- clearInterval(startedInterbal);
46
- });
47
-
48
- const currentLogLevel = $derived(client.watchReport(new ServerLogLevel({ serverId: server.id })));
49
- </script>
50
-
51
- <div class="server flex gap-5">
52
- <div class="info">
53
- <span class="address">{server.address}:{server.port}</span>
54
- <span class="version">{server.version}</span>
55
- <span class="id">ID: {server.id}</span>
56
- <span class="started">Started: {started}</span>
57
- <span>Ping {isClientServer ? '' : 'to connected'}: {ping}</span>
58
- <select
59
- aria-label="Log Level"
60
- class="select"
61
- onchange={(e) => {
62
- console.log(e.currentTarget.value);
63
- client.sendCommand(
64
- new SetLogLevel({
65
- serverId: server.id,
66
- level: e.currentTarget.value as LogLevel
67
- })
68
- );
69
- }}
70
- >
71
- {#each LOG_RANK as rank}
72
- <option selected={rank === $currentLogLevel} value={rank}>{rank}</option>
73
- {/each}
74
- </select>
75
- </div>
76
- </div>
77
-
78
- <style>
79
- .server {
80
- position: relative;
81
- }
82
-
83
- span {
84
- display: block;
85
- white-space: nowrap;
86
- }
87
-
88
- .version {
89
- opacity: 0.5;
90
- }
91
-
92
- .id {
93
- white-space: nowrap;
94
- }
95
- </style>