@lobb-js/studio 0.28.5 → 0.29.0

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 (92) hide show
  1. package/dist/components/Studio.svelte +7 -4
  2. package/dist/components/combobox.svelte +3 -3
  3. package/dist/components/confirmationDialog/confirmationDialog.svelte +1 -1
  4. package/dist/components/dataTable/dataTable.svelte +74 -82
  5. package/dist/components/dataTable/dataTable.svelte.d.ts +3 -19
  6. package/dist/components/dataTable/filter.svelte +1 -1
  7. package/dist/components/dataTable/filterButton.svelte +1 -1
  8. package/dist/components/dataTable/header.svelte +33 -54
  9. package/dist/components/dataTable/header.svelte.d.ts +3 -2
  10. package/dist/components/dataTable/sort.svelte +1 -1
  11. package/dist/components/dataTable/sortButton.svelte +2 -2
  12. package/dist/components/detailView/create/children.svelte +1 -1
  13. package/dist/components/detailView/create/createDetailView.svelte +77 -42
  14. package/dist/components/detailView/create/createDetailView.svelte.d.ts +2 -2
  15. package/dist/components/detailView/create/createDetailViewButton.svelte +2 -2
  16. package/dist/components/detailView/create/createDetailViewButton.svelte.d.ts +1 -1
  17. package/dist/components/detailView/create/createManyView.svelte +10 -6
  18. package/dist/components/detailView/fieldInput.svelte +1 -1
  19. package/dist/components/detailView/fieldInputReplacement.svelte +1 -1
  20. package/dist/components/detailView/update/detailViewChildren.svelte +15 -26
  21. package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +3 -8
  22. package/dist/components/detailView/update/updateDetailView.svelte +85 -27
  23. package/dist/components/detailView/update/updateDetailView.svelte.d.ts +2 -2
  24. package/dist/components/detailView/update/updateDetailViewButton.svelte +3 -2
  25. package/dist/components/detailView/update/updateDetailViewButton.svelte.d.ts +1 -1
  26. package/dist/components/detailView/utils.d.ts +17 -0
  27. package/dist/components/miniSidebar.svelte +4 -4
  28. package/dist/components/rangeCalendarButton.svelte +3 -3
  29. package/dist/components/routes/collections/collection.svelte +3 -3
  30. package/dist/components/routes/collections/collections.svelte +2 -2
  31. package/dist/components/routes/data_model/dataModel.svelte +2 -2
  32. package/dist/components/routes/data_model/syncManager.svelte +4 -4
  33. package/dist/components/routes/extensions/extension.svelte +1 -1
  34. package/dist/components/routes/home.svelte +1 -1
  35. package/dist/components/routes/workflows/workflows.svelte +5 -5
  36. package/dist/components/selectRecord.svelte +2 -21
  37. package/dist/components/setServerPage.svelte +1 -1
  38. package/dist/components/ui/alert-dialog/alert-dialog-action.svelte +1 -1
  39. package/dist/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
  40. package/dist/components/ui/command/command-dialog.svelte +1 -1
  41. package/dist/components/ui/range-calendar/range-calendar-day.svelte +1 -1
  42. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
  43. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
  44. package/dist/components/ui/select/select-separator.svelte +1 -1
  45. package/dist/components/workflowEditor.svelte +3 -3
  46. package/dist/store.types.d.ts +1 -1
  47. package/package.json +2 -2
  48. package/src/lib/components/Studio.svelte +7 -4
  49. package/src/lib/components/combobox.svelte +3 -3
  50. package/src/lib/components/confirmationDialog/confirmationDialog.svelte +1 -1
  51. package/src/lib/components/dataTable/dataTable.svelte +74 -82
  52. package/src/lib/components/dataTable/filter.svelte +1 -1
  53. package/src/lib/components/dataTable/filterButton.svelte +1 -1
  54. package/src/lib/components/dataTable/header.svelte +33 -54
  55. package/src/lib/components/dataTable/sort.svelte +1 -1
  56. package/src/lib/components/dataTable/sortButton.svelte +2 -2
  57. package/src/lib/components/detailView/create/children.svelte +1 -1
  58. package/src/lib/components/detailView/create/createDetailView.svelte +77 -42
  59. package/src/lib/components/detailView/create/createDetailViewButton.svelte +2 -2
  60. package/src/lib/components/detailView/create/createManyView.svelte +10 -6
  61. package/src/lib/components/detailView/fieldInput.svelte +1 -1
  62. package/src/lib/components/detailView/fieldInputReplacement.svelte +1 -1
  63. package/src/lib/components/detailView/update/detailViewChildren.svelte +15 -26
  64. package/src/lib/components/detailView/update/updateDetailView.svelte +85 -27
  65. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +3 -2
  66. package/src/lib/components/detailView/utils.ts +13 -0
  67. package/src/lib/components/miniSidebar.svelte +4 -4
  68. package/src/lib/components/rangeCalendarButton.svelte +3 -3
  69. package/src/lib/components/routes/collections/collection.svelte +3 -3
  70. package/src/lib/components/routes/collections/collections.svelte +2 -2
  71. package/src/lib/components/routes/data_model/dataModel.svelte +2 -2
  72. package/src/lib/components/routes/data_model/syncManager.svelte +4 -4
  73. package/src/lib/components/routes/extensions/extension.svelte +1 -1
  74. package/src/lib/components/routes/home.svelte +1 -1
  75. package/src/lib/components/routes/workflows/workflows.svelte +5 -5
  76. package/src/lib/components/selectRecord.svelte +2 -21
  77. package/src/lib/components/setServerPage.svelte +1 -1
  78. package/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte +1 -1
  79. package/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte +1 -1
  80. package/src/lib/components/ui/command/command-dialog.svelte +1 -1
  81. package/src/lib/components/ui/range-calendar/range-calendar-day.svelte +1 -1
  82. package/src/lib/components/ui/range-calendar/range-calendar-next-button.svelte +1 -1
  83. package/src/lib/components/ui/range-calendar/range-calendar-prev-button.svelte +1 -1
  84. package/src/lib/components/ui/select/select-separator.svelte +1 -1
  85. package/src/lib/components/workflowEditor.svelte +3 -3
  86. package/src/lib/store.types.ts +1 -1
  87. package/vite-plugins/index.js +2 -4
  88. package/vite-plugins/lobb-extensions.js +12 -0
  89. package/vite-plugins/utils.js +15 -0
  90. package/vite-plugins/workspace-fs-allow.js +33 -0
  91. package/vite-plugins/contextual-lib-alias.js +0 -67
  92. package/vite-plugins/workspace-optimize.js +0 -106
@@ -11,9 +11,9 @@
11
11
  today,
12
12
  } from "@internationalized/date";
13
13
  import { cn } from "../utils.js";
14
- import { buttonVariants } from "../components/ui/button/index.js";
15
- import { RangeCalendar } from "../components/ui/range-calendar/index.js";
16
- import * as Popover from "../components/ui/popover/index.js";
14
+ import { buttonVariants } from "./ui/button/index.js";
15
+ import { RangeCalendar } from "./ui/range-calendar/index.js";
16
+ import * as Popover from "./ui/popover/index.js";
17
17
  import Input from "./ui/input/input.svelte";
18
18
 
19
19
  interface Props {
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { CircleSlash2, Zap } from "lucide-svelte";
3
- import DataTable from "../../../components/dataTable/dataTable.svelte";
4
- import SidebarTrigger from "../../../components/sidebar/sidebarTrigger.svelte";
3
+ import DataTable from "../../dataTable/dataTable.svelte";
4
+ import SidebarTrigger from "../../sidebar/sidebarTrigger.svelte";
5
5
  import { getStudioContext } from "../../../context";
6
- import Singletone from "../../../components/singletone.svelte";
6
+ import Singletone from "../../singletone.svelte";
7
7
 
8
8
  const { ctx } = getStudioContext();
9
9
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import type { SideBarData, SideBarNode } from "../../../components/sidebar/sidebarElements.svelte";
3
- import Sidebar from "../../../components/sidebar/sidebar.svelte";
2
+ import type { SideBarData, SideBarNode } from "../../sidebar/sidebarElements.svelte";
3
+ import Sidebar from "../../sidebar/sidebar.svelte";
4
4
  import { getStudioContext } from "../../../context";
5
5
  import Collection from "./collection.svelte";
6
6
 
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { SvelteFlowProvider } from "@xyflow/svelte";
3
3
  import Flow from "./flow.svelte";
4
- import Sidebar from "../../../components/sidebar/sidebar.svelte";
4
+ import Sidebar from "../../sidebar/sidebar.svelte";
5
5
  import { location } from "@wjfe/n-savant";
6
6
  import SyncManager from "./syncManager.svelte";
7
- import SidebarTrigger from "../../../components/sidebar/sidebarTrigger.svelte";
7
+ import SidebarTrigger from "../../sidebar/sidebarTrigger.svelte";
8
8
 
9
9
  const currentPage = $derived(location.url.pathname.replace("/studio", "").split("/")[2]);
10
10
  </script>
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
- import DiffViewer from "../../../components/diffViewer.svelte";
2
+ import DiffViewer from "../../diffViewer.svelte";
3
3
  import { getStudioContext } from "../../../context";
4
4
 
5
5
  const { lobb, ctx } = getStudioContext();
6
6
  import { onMount } from "svelte";
7
7
  import stringify from "json-stable-stringify";
8
- import CodeEditor from "../../../components/codeEditor.svelte";
9
- import Table from "../../../components/dataTable/table.svelte";
10
- import Button from "../../../components/ui/button/button.svelte";
8
+ import CodeEditor from "../../codeEditor.svelte";
9
+ import Table from "../../dataTable/table.svelte";
10
+ import Button from "../../ui/button/button.svelte";
11
11
  import { LoaderCircle, SendHorizontal } from "lucide-svelte";
12
12
 
13
13
  let configSchema: string = $state("");
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import ExtensionsComponents from "../../../components/extensionsComponents.svelte";
2
+ import ExtensionsComponents from "../../extensionsComponents.svelte";
3
3
  import { getExtensionUtils } from "../../../extensions/extensionUtils";
4
4
  import { getStudioContext } from "../../../context";
5
5
 
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import Button from "../../components/ui/button/button.svelte";
2
+ import Button from "../ui/button/button.svelte";
3
3
  import { location } from "@wjfe/n-savant";
4
4
  import { ArrowRight } from "lucide-svelte";
5
5
  import HomeFooter from "./homeFooter.svelte";
@@ -1,18 +1,18 @@
1
1
  <script lang="ts">
2
- import type { SideBarData, SideBarNode } from "../../../components/sidebar/sidebarElements.svelte";
2
+ import type { SideBarData, SideBarNode } from "../../sidebar/sidebarElements.svelte";
3
3
  import WorkflowEditor, {
4
4
  type WorkflowEntry,
5
- } from "../../../components/workflowEditor.svelte";
5
+ } from "../../workflowEditor.svelte";
6
6
  import { getStudioContext } from "../../../context";
7
7
 
8
8
  const { lobb, ctx } = getStudioContext();
9
- import Sidebar from "../../../components/sidebar/sidebar.svelte";
10
- import Button from "../../../components/ui/button/button.svelte";
9
+ import Sidebar from "../../sidebar/sidebar.svelte";
10
+ import Button from "../../ui/button/button.svelte";
11
11
  import { location } from "@wjfe/n-savant";
12
12
  import { CircleSlash2, Plus, Trash2 } from "lucide-svelte";
13
13
  import { onMount } from "svelte";
14
14
  import { showDialog } from "../../../actions";
15
- import SidebarTrigger from "../../../components/sidebar/sidebarTrigger.svelte";
15
+ import SidebarTrigger from "../../sidebar/sidebarTrigger.svelte";
16
16
 
17
17
  let { workflowName } = $props();
18
18
 
@@ -3,7 +3,6 @@
3
3
  import Button, { type ButtonProps } from "./ui/button/button.svelte";
4
4
  import DataTable from "./dataTable/dataTable.svelte";
5
5
  import { getCollectionPrimaryField } from "./dataTable/utils";
6
- import { emitEvent } from "../eventSystem";
7
6
  import { getStudioContext } from "../context";
8
7
  import Drawer from "./drawer.svelte";
9
8
 
@@ -36,26 +35,8 @@
36
35
 
37
36
  let openDrawer = $state(false);
38
37
 
39
- async function handleButtonClick() {
40
- try {
41
- const eventResult = await emitEvent(
42
- { lobb, ctx },
43
- "studio.collections.preForeignKeySelect",
44
- {
45
- parentCollectionName,
46
- collectionName,
47
- fieldName,
48
- entry,
49
- },
50
- );
51
- if (eventResult.filter) {
52
- additionalFilter = eventResult.filter;
53
- }
54
-
55
- openDrawer = true;
56
- } catch (error) {
57
- console.error(error);
58
- }
38
+ function handleButtonClick() {
39
+ openDrawer = true;
59
40
  }
60
41
 
61
42
  async function onSelectHandler(entry: any) {
@@ -2,7 +2,7 @@
2
2
  import { toast } from "svelte-sonner";
3
3
  import Button from "./ui/button/button.svelte";
4
4
  import { getStudioContext } from "../context";
5
- import { Input } from "../components/ui/input";
5
+ import { Input } from "./ui/input";
6
6
 
7
7
  const { ctx } = getStudioContext();
8
8
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
- import { buttonVariants } from "../../../components/ui/button/index.js";
3
+ import { buttonVariants } from "../button/index.js";
4
4
  import { cn } from "../../../utils.js";
5
5
 
6
6
  let {
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
3
- import { buttonVariants } from "../../../components/ui/button/index.js";
3
+ import { buttonVariants } from "../button/index.js";
4
4
  import { cn } from "../../../utils.js";
5
5
 
6
6
  let {
@@ -6,7 +6,7 @@
6
6
  } from "bits-ui";
7
7
  import type { Snippet } from "svelte";
8
8
  import Command from "./command.svelte";
9
- import * as Dialog from "../../../components/ui/dialog/index.js";
9
+ import * as Dialog from "../dialog/index.js";
10
10
 
11
11
  let {
12
12
  open = $bindable(false),
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui";
3
- import { buttonVariants } from "../../../components/ui/button/index.js";
3
+ import { buttonVariants } from "../button/index.js";
4
4
  import { cn } from "../../../utils.js";
5
5
 
6
6
  let {
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui";
3
3
  import ChevronRight from "@lucide/svelte/icons/chevron-right";
4
- import { buttonVariants } from "../../../components/ui/button/index.js";
4
+ import { buttonVariants } from "../button/index.js";
5
5
  import { cn } from "../../../utils.js";
6
6
  let {
7
7
  ref = $bindable(null),
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { RangeCalendar as RangeCalendarPrimitive } from "bits-ui";
3
3
  import ChevronLeft from "@lucide/svelte/icons/chevron-left";
4
- import { buttonVariants } from "../../../components/ui/button/index.js";
4
+ import { buttonVariants } from "../button/index.js";
5
5
  import { cn } from "../../../utils.js";
6
6
  let {
7
7
  ref = $bindable(null),
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Separator as SeparatorPrimitive } from "bits-ui";
3
- import { Separator } from "../../../components/ui/separator/index.js";
3
+ import { Separator } from "../separator/index.js";
4
4
  import { cn } from "../../../utils.js";
5
5
 
6
6
  let {
@@ -12,11 +12,11 @@
12
12
 
13
13
  <script lang="ts">
14
14
  import { getStudioContext } from "../context";
15
- import CodeEditor from "../components/codeEditor.svelte";
15
+ import CodeEditor from "./codeEditor.svelte";
16
16
 
17
17
  const { lobb, ctx } = getStudioContext();
18
- import Button from "../components/ui/button/button.svelte";
19
- import Input from "../components/ui/input/input.svelte";
18
+ import Button from "./ui/button/button.svelte";
19
+ import Input from "./ui/input/input.svelte";
20
20
  import { location } from "@wjfe/n-savant";
21
21
  import { Edit, Plus } from "lucide-svelte";
22
22
  import { toast } from "svelte-sonner";
@@ -1,4 +1,4 @@
1
- import type { Extension } from "../extensions/extension.types";
1
+ import type { Extension } from "./extensions/extension.types";
2
2
  interface CollectionTab {
3
3
  id?: string;
4
4
  label: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
3
  "license": "UNLICENSED",
4
- "version": "0.28.5",
4
+ "version": "0.29.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -87,7 +87,7 @@
87
87
  "@codemirror/view": "^6.39.12",
88
88
  "@dagrejs/dagre": "^1.1.5",
89
89
  "@internationalized/date": "^3.12.0",
90
- "@lobb-js/sdk": "^0.1.6",
90
+ "@lobb-js/sdk": "^0.2.0",
91
91
  "@lucide/svelte": "^0.563.1",
92
92
  "@tailwindcss/vite": "^4.3.0",
93
93
  "@tiptap/core": "^3.0.0",
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
- import { Toaster } from "../components/ui/sonner";
2
+ import { Toaster } from "./ui/sonner";
3
3
  import { onMount, onDestroy } from "svelte";
4
4
  import { ModeWatcher } from "mode-watcher";
5
5
  import { createLobb } from "../store.svelte";
6
6
  import { setStudioContext } from "../context";
7
- import Header from "../components/header.svelte";
7
+ import Header from "./header.svelte";
8
8
  import { LoaderCircle, ServerOff } from "lucide-svelte";
9
- import MiniSidebar from "../components/miniSidebar.svelte";
10
- import * as Tooltip from "../components/ui/tooltip";
9
+ import MiniSidebar from "./miniSidebar.svelte";
10
+ import * as Tooltip from "./ui/tooltip";
11
11
  import { Router, Route, Fallback, init as initRouter } from "@wjfe/n-savant";
12
12
  import {
13
13
  executeExtensionsOnStartup,
@@ -45,6 +45,9 @@
45
45
  let cleanupRouter: (() => void) | undefined;
46
46
 
47
47
  onMount(async () => {
48
+ // Remove the static loading screen defined in app.html — it shows instantly
49
+ // before JS loads to avoid a blank page, and is replaced by the Studio UI.
50
+ document.getElementById("app-loading")?.remove();
48
51
  cleanupRouter = initRouter();
49
52
  try {
50
53
  ctx.meta = await lobb.getMeta();
@@ -2,9 +2,9 @@
2
2
  import CheckIcon from "@lucide/svelte/icons/check";
3
3
  import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
4
4
  import { tick } from "svelte";
5
- import * as Command from "../components/ui/command/index.js";
6
- import * as Popover from "../components/ui/popover/index.js";
7
- import { Button } from "../components/ui/button/index.js";
5
+ import * as Command from "./ui/command/index.js";
6
+ import * as Popover from "./ui/popover/index.js";
7
+ import { Button } from "./ui/button/index.js";
8
8
  import { cn } from "../utils.js";
9
9
  import type { HTMLButtonAttributes } from "svelte/elements";
10
10
 
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import * as AlertDialog from "../../components/ui/alert-dialog/index";
2
+ import * as AlertDialog from "../ui/alert-dialog/index";
3
3
  import { Check, X } from "lucide-svelte";
4
4
  import Button from "../ui/button/button.svelte";
5
5
 
@@ -4,12 +4,6 @@
4
4
  recordId: string | number;
5
5
  }
6
6
 
7
- export type RecordOperation =
8
- | { type: "link"; record: any }
9
- | { type: "unlink"; id: string | number }
10
- | { type: "delete"; id: string | number }
11
- | { type: "create"; record: any }
12
- | { type: "update"; id: string | number; data: any };
13
7
  </script>
14
8
 
15
9
  <script lang="ts">
@@ -20,15 +14,14 @@
20
14
  import Table, { type TableProps } from "./table.svelte";
21
15
  import { getCollectionColumns, getCollectionParamsFields } from "./utils";
22
16
  import { Pencil, Trash, Unlink } from "lucide-svelte";
23
- import * as icons from "lucide-svelte";
24
17
  import ListViewChildren from "./listViewChildren.svelte";
25
18
  import FieldCell from "./fieldCell.svelte";
26
19
  import Skeleton from "../ui/skeleton/skeleton.svelte";
27
20
  import Button from "../ui/button/button.svelte";
28
21
  import { showDialog } from "../../actions";
29
22
  import UpdateDetailViewButton from "../detailView/update/updateDetailViewButton.svelte";
30
- import { emitEvent } from "../../eventSystem";
31
23
  import type { Snippet } from "svelte";
24
+ import type { Changes, ChildrenChanges } from "../detailView/utils";
32
25
  import ExtensionsComponents from "../extensionsComponents.svelte";
33
26
  import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
34
27
  import Tabs from "./dataTableTabs.svelte";
@@ -41,7 +34,7 @@
41
34
  filter?: any;
42
35
  searchParams?: Record<string, any>;
43
36
  parentContext?: ParentContext;
44
- onOperation?: (op: RecordOperation) => void;
37
+ changes?: ChildrenChanges;
45
38
  showHeader?: boolean;
46
39
  showFooter?: boolean;
47
40
  showImport?: boolean;
@@ -56,7 +49,7 @@
56
49
  filter,
57
50
  searchParams,
58
51
  parentContext,
59
- onOperation,
52
+ changes = $bindable<ChildrenChanges | undefined>(undefined),
60
53
  showHeader = true,
61
54
  showFooter = true,
62
55
  showImport = true,
@@ -66,6 +59,46 @@
66
59
  headerLeft,
67
60
  }: Props = $props();
68
61
 
62
+ function getOrCreateUpdatedSlot(recordId: string): Changes | undefined {
63
+ if (!changes) return undefined;
64
+ let slot = changes.updated.find((u) => String(u.id) === String(recordId));
65
+ if (!slot) {
66
+ slot = { id: recordId, data: {}, children: {} };
67
+ changes.updated.push(slot);
68
+ }
69
+ return slot;
70
+ }
71
+
72
+ // Derives the displayed rows by applying changes on top of server data.
73
+ // This is the single place responsible for optimistic UI — no handler touches data directly.
74
+ const data = $derived.by(() => {
75
+ if (!changes) return serverData;
76
+
77
+ const removedIds = new Set([
78
+ ...changes.deleted.map((r) => String(r.id)),
79
+ ...changes.unlinked.map((r) => String(r.id)),
80
+ ]);
81
+
82
+ let result = serverData.filter((r: any) => !removedIds.has(String(r.id)));
83
+
84
+ result = result.map((r: any) => {
85
+ const update = changes.updated.find((u) => String(u.id) === String(r.id));
86
+ return update && Object.keys(update.data).length ? { ...r, ...update.data } : r;
87
+ });
88
+
89
+ for (const record of changes.linked) {
90
+ if (!result.some((r: any) => String(r.id) === String(record.id))) {
91
+ result = [...result, record];
92
+ }
93
+ }
94
+
95
+ for (const item of changes.created) {
96
+ result = [...result, { ...item.data, _pending: true }];
97
+ }
98
+
99
+ return result;
100
+ });
101
+
69
102
  const hasRowActions = $derived(
70
103
  loadExtensionComponents(ctx, "listView.entry.actions", undefined, { collectionName }).length > 0
71
104
  );
@@ -89,7 +122,7 @@
89
122
 
90
123
  let selectedRecords = $state([]);
91
124
  let totalCount = $state(0);
92
- let data: TableProps["data"] = $state([]);
125
+ let serverData: TableProps["data"] = $state([]);
93
126
  let loading = $state(true);
94
127
  const columns: TableProps["columns"] = $state(
95
128
  getCollectionColumns(ctx, collectionName),
@@ -108,7 +141,6 @@
108
141
 
109
142
  async function loadData(params: any) {
110
143
  loading = true;
111
- // parsing sort before sending the request
112
144
  const paramsCopy = $state.snapshot(params);
113
145
  const sort: TableProps["sort"] = paramsCopy.sort;
114
146
  const sortStrings: string[] = [];
@@ -118,77 +150,43 @@
118
150
  }
119
151
  }
120
152
  paramsCopy.sort = sortStrings.join(",");
121
-
122
- // sending the request
123
153
  const response = await lobb.findAll(collectionName, paramsCopy);
124
154
  const res = await response.json();
125
-
126
- data = res.data;
155
+ serverData = res.data;
127
156
  totalCount = res.meta.totalCount;
128
-
129
157
  loading = false;
130
158
  }
131
159
 
132
- // Internal handler: updates data optimistically then calls onOperation
133
- function applyOperation(op: RecordOperation) {
134
- if (op.type === "link") {
135
- data = [...data, op.record];
136
- } else if (op.type === "unlink" || op.type === "delete") {
137
- data = data.filter((r: any) => String(r.id) !== String(op.id));
138
- } else if (op.type === "create") {
139
- data = [...data, { ...op.record, _pending: true }];
140
- } else if (op.type === "update") {
141
- data = data.map((r: any) => String(r.id) === String(op.id) ? { ...r, ...op.data } : r);
142
- }
143
- onOperation?.(op);
144
- }
145
-
146
160
  async function handleDelete(entryId: string) {
147
161
  const result = await showDialog("Are you sure?", "This will permanently delete the record.");
148
- if (result) {
149
- if (onOperation) {
150
- applyOperation({ type: "delete", id: entryId });
151
- } else if (parentContext) {
152
- await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
153
- params = { ...params };
154
- } else {
155
- await lobb.deleteOne(collectionName, entryId);
156
- params = { ...params };
157
- }
162
+ if (!result) return;
163
+ if (changes) {
164
+ const record = data.find((r: any) => String(r.id) === String(entryId));
165
+ if (record) changes.deleted.push($state.snapshot(record));
166
+ } else if (parentContext) {
167
+ serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
168
+ await lobb.updateOne(parentContext.collectionName, String(parentContext.recordId), {}, { [collectionName]: { delete: [entryId] } });
169
+ params = { ...params };
170
+ } else {
171
+ serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
172
+ await lobb.deleteOne(collectionName, entryId);
173
+ params = { ...params };
158
174
  }
159
175
  }
160
176
 
161
177
  async function handleUnlink(entryId: string) {
162
178
  const result = await showDialog("Are you sure?", "This will unlink the record without deleting it.");
163
- if (result) {
164
- if (onOperation) {
165
- applyOperation({ type: "unlink", id: entryId });
166
- } else {
167
- await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
168
- params = { ...params };
169
- }
179
+ if (!result) return;
180
+ if (changes) {
181
+ const record = data.find((r: any) => String(r.id) === String(entryId));
182
+ if (record) changes.unlinked.push($state.snapshot(record));
183
+ } else {
184
+ serverData = serverData.filter((r: any) => String(r.id) !== String(entryId));
185
+ await lobb.updateOne(parentContext!.collectionName, String(parentContext!.recordId), {}, { [collectionName]: { unlink: [entryId] } });
186
+ params = { ...params };
170
187
  }
171
188
  }
172
189
 
173
- async function getWorkflowTools(
174
- entry: Record<string, any>,
175
- ): Promise<any[]> {
176
- // TODO: instead of firing the events like this. get them all the fire them one by one to get their results
177
- const eventResult = await emitEvent(
178
- { lobb, ctx },
179
- "studio.collections.listView.tools",
180
- {
181
- collectionName,
182
- entry,
183
- },
184
- );
185
-
186
- if (eventResult) {
187
- return eventResult.tools;
188
- }
189
-
190
- return [];
191
- }
192
190
  </script>
193
191
 
194
192
  <div
@@ -209,7 +207,14 @@
209
207
  {/snippet}
210
208
 
211
209
  {#if showHeader}
212
- <Header bind:params {collectionName} bind:selectedRecords {parentContext} {showImport} onOperation={onOperation ? applyOperation : undefined}>
210
+ <Header
211
+ bind:params
212
+ {collectionName}
213
+ bind:selectedRecords
214
+ {showImport}
215
+ {parentContext}
216
+ {changes}
217
+ >
213
218
  {#snippet left()}
214
219
  {@render headerLeft?.()}
215
220
  {/snippet}
@@ -246,6 +251,7 @@
246
251
  variant="ghost"
247
252
  class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
248
253
  Icon={Pencil}
254
+ changes={getOrCreateUpdatedSlot(String(entry.id))}
249
255
  onSuccessfullSave={async () => {
250
256
  params = { ...params };
251
257
  }}
@@ -270,20 +276,6 @@
270
276
  title="Delete permanently"
271
277
  ></Button>
272
278
  {/if}
273
- {#await getWorkflowTools($state.snapshot(entry))}
274
- <div></div>
275
- {:then workflowTools}
276
- {#each workflowTools as workflowTool}
277
- <Button
278
- variant="ghost"
279
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
280
- Icon={icons[
281
- workflowTool.icon as keyof typeof icons
282
- ]}
283
- onclick={workflowTool.onclick}
284
- ></Button>
285
- {/each}
286
- {/await}
287
279
  {/snippet}
288
280
  {#snippet overrideCell(value, column, entry)}
289
281
  <FieldCell
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import * as Popover from "../../components/ui/popover/index.js";
2
+ import * as Popover from "../ui/popover/index.js";
3
3
  import Filter from "./filter.svelte";
4
4
  import {
5
5
  Plus,
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import * as Popover from "../../components/ui/popover/index.js";
2
+ import * as Popover from "../ui/popover/index.js";
3
3
  import { ListFilter } from "lucide-svelte";
4
4
  import { buttonVariants } from "../ui/button";
5
5
  import Filter from "./filter.svelte";