@lobb-js/studio 0.13.0 → 0.14.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.
@@ -18,6 +18,7 @@
18
18
  import ExtensionsComponents from "../extensionsComponents.svelte";
19
19
  import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
20
20
  import Tabs from "./dataTableTabs.svelte";
21
+ import { fade } from "svelte/transition";
21
22
 
22
23
  const { lobb, ctx } = getStudioContext();
23
24
 
@@ -84,6 +85,7 @@
84
85
  });
85
86
 
86
87
  async function loadData(params: any) {
88
+ loading = true;
87
89
  // parsing sort before sending the request
88
90
  const paramsCopy = $state.snapshot(params);
89
91
  const sort: TableProps["sort"] = paramsCopy.sort;
@@ -154,7 +156,6 @@
154
156
  />
155
157
  {/snippet}
156
158
 
157
- <Tabs {collectionName} {filter} bind:activeTabFilter />
158
159
  {#if showHeader}
159
160
  <Header bind:params {collectionName} bind:selectedRecords>
160
161
  {#snippet left()}
@@ -162,83 +163,88 @@
162
163
  {/snippet}
163
164
  </Header>
164
165
  {/if}
166
+ <Tabs {collectionName} {filter} bind:activeTabFilter />
165
167
  <div class="relative flex-1 overflow-auto w-full">
166
- {#if loading}
167
- <div class="flex flex-col gap-2 p-2 w-full">
168
- <Skeleton class="h-8 w-full" />
169
- <Skeleton class="h-8 w-[80%]" />
170
- <Skeleton class="h-8 w-[60%]" />
171
- </div>
172
- {:else}
173
- <Table
174
- {data}
175
- {columns}
176
- showCollapsible={doesCollectionHasChildren}
177
- selectByColumn="id"
178
- showLastRowBorder={true}
179
- showLastColumnBorder={true}
180
- bind:sort={params.sort}
181
- bind:selectedRecords
182
- {unifiedBgColor}
183
- bind:tableWidth={dataTableWidth}
184
- {...tableProps}
185
- rowActions={hasRowActions ? rowActionsSnippet : undefined}>
186
- {#snippet tools(entry)}
187
- <UpdateDetailViewButton
188
- {collectionName}
189
- recordId={entry.id}
190
- variant="ghost"
191
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
192
- Icon={Pencil}
193
- onSuccessfullSave={async () => {
194
- params = { ...params };
195
- }}
196
- ></UpdateDetailViewButton>
197
- {#if showDelete}
198
- <Button
199
- class="h-6 w-6 text-muted-foreground hover:bg-transparent"
200
- variant="ghost"
201
- size="icon"
202
- onclick={() => handleDelete(entry.id)}
203
- Icon={Trash}
204
- ></Button>
205
- {/if}
206
- {#await getWorkflowTools($state.snapshot(entry))}
207
- <div></div>
208
- {:then workflowTools}
209
- {#each workflowTools as workflowTool}
210
- <Button
168
+ {#key activeTabFilter}
169
+ <div class="h-full w-full" in:fade={{ duration: 120 }}>
170
+ {#if loading}
171
+ <div class="flex flex-col gap-2 p-2 w-full">
172
+ <Skeleton class="h-8 w-full" />
173
+ <Skeleton class="h-8 w-[80%]" />
174
+ <Skeleton class="h-8 w-[60%]" />
175
+ </div>
176
+ {:else}
177
+ <Table
178
+ {data}
179
+ {columns}
180
+ showCollapsible={doesCollectionHasChildren}
181
+ selectByColumn="id"
182
+ showLastRowBorder={true}
183
+ showLastColumnBorder={true}
184
+ bind:sort={params.sort}
185
+ bind:selectedRecords
186
+ {unifiedBgColor}
187
+ bind:tableWidth={dataTableWidth}
188
+ {...tableProps}
189
+ rowActions={hasRowActions ? rowActionsSnippet : undefined}>
190
+ {#snippet tools(entry)}
191
+ <UpdateDetailViewButton
192
+ {collectionName}
193
+ recordId={entry.id}
211
194
  variant="ghost"
212
195
  class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
213
- Icon={icons[
214
- workflowTool.icon as keyof typeof icons
215
- ]}
216
- onclick={workflowTool.onclick}
217
- ></Button>
218
- {/each}
219
- {/await}
220
- {/snippet}
221
- {#snippet overrideCell(value, column, entry)}
222
- <FieldCell
223
- {collectionName}
224
- fieldName={column.id}
225
- {value}
226
- {entry}
227
- tableParams={params}
228
- />
229
- {/snippet}
230
- {#snippet collapsible(entry)}
231
- <ChildRecords
232
- {collectionName}
233
- recordId={entry.id}
234
- width={dataTableWidth > dataTableContainerWidth
235
- ? dataTableContainerWidth
236
- : dataTableWidth}
237
- unifiedBgColor={unifiedBgColor ?? "bg-background"}
238
- />
239
- {/snippet}
240
- </Table>
241
- {/if}
196
+ Icon={Pencil}
197
+ onSuccessfullSave={async () => {
198
+ params = { ...params };
199
+ }}
200
+ ></UpdateDetailViewButton>
201
+ {#if showDelete}
202
+ <Button
203
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
204
+ variant="ghost"
205
+ size="icon"
206
+ onclick={() => handleDelete(entry.id)}
207
+ Icon={Trash}
208
+ ></Button>
209
+ {/if}
210
+ {#await getWorkflowTools($state.snapshot(entry))}
211
+ <div></div>
212
+ {:then workflowTools}
213
+ {#each workflowTools as workflowTool}
214
+ <Button
215
+ variant="ghost"
216
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
217
+ Icon={icons[
218
+ workflowTool.icon as keyof typeof icons
219
+ ]}
220
+ onclick={workflowTool.onclick}
221
+ ></Button>
222
+ {/each}
223
+ {/await}
224
+ {/snippet}
225
+ {#snippet overrideCell(value, column, entry)}
226
+ <FieldCell
227
+ {collectionName}
228
+ fieldName={column.id}
229
+ {value}
230
+ {entry}
231
+ tableParams={params}
232
+ />
233
+ {/snippet}
234
+ {#snippet collapsible(entry)}
235
+ <ChildRecords
236
+ {collectionName}
237
+ recordId={entry.id}
238
+ width={dataTableWidth > dataTableContainerWidth
239
+ ? dataTableContainerWidth
240
+ : dataTableWidth}
241
+ unifiedBgColor={unifiedBgColor ?? "bg-background"}
242
+ />
243
+ {/snippet}
244
+ </Table>
245
+ {/if}
246
+ </div>
247
+ {/key}
242
248
  </div>
243
249
  {#if showFooter}
244
250
  <Footer
@@ -10,6 +10,8 @@
10
10
  import CreateManyButton from "../createManyButton.svelte";
11
11
  import { showDialog } from "../confirmationDialog/store.svelte";
12
12
  import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
13
+ import ExtensionsComponents from "../extensionsComponents.svelte";
14
+ import { getExtensionUtils } from "../../extensions/extensionUtils";
13
15
  import type { Snippet } from "svelte";
14
16
 
15
17
  interface Props {
@@ -142,6 +144,12 @@
142
144
  Icon={SquareStack}
143
145
  onSuccessfullSave={() => (params = { ...params })}
144
146
  ></CreateManyButton>
147
+ <ExtensionsComponents
148
+ name="listView.header.actions"
149
+ utils={getExtensionUtils(lobb, ctx)}
150
+ {collectionName}
151
+ refresh={() => { params = { ...params }; }}
152
+ />
145
153
  <CreateDetailViewButton
146
154
  {collectionName}
147
155
  variant="default"
@@ -6,30 +6,27 @@
6
6
  import Button from "./ui/button/button.svelte";
7
7
  import { onMount } from "svelte";
8
8
  import { getStudioContext } from "../context";
9
+ import { toast } from "svelte-sonner";
10
+ import Skeleton from "./ui/skeleton/skeleton.svelte";
9
11
 
10
12
  const { lobb, ctx } = getStudioContext();
11
- import { toast } from "svelte-sonner";
12
- import Skeleton from "./ui/skeleton/skeleton.svelte";
13
13
 
14
14
  interface Props {
15
15
  collectionName: string;
16
16
  }
17
17
 
18
- let {
19
- collectionName,
20
- }: Props = $props();
18
+ let { collectionName }: Props = $props();
21
19
 
22
20
  let entry = $state({});
23
21
  let loading = $state(true);
24
- let singletonExists = $state(true);
25
22
  const formFields = getCollectionFields(ctx, collectionName);
26
23
 
27
24
  onMount(async () => {
28
- const result = await lobb.readSingleton(collectionName);
29
- const json = await result.json();
30
- if (result.status === 404) {
31
- singletonExists = false;
32
- } else {
25
+ const result = await fetch(`${lobb.lobbUrl}/api/collections/${collectionName}/singleton`, {
26
+ headers: lobb.getHeaders() as HeadersInit,
27
+ });
28
+ if (result.status !== 404) {
29
+ const json = await result.json();
33
30
  entry = json.data;
34
31
  }
35
32
  loading = false;
@@ -43,29 +40,27 @@
43
40
  }
44
41
  </script>
45
42
 
46
- <div>
47
- <div class="flex justify-between items-center gap-2 p-2 border-b bg-background h-10">
43
+ <div class="flex flex-col h-full bg-background">
44
+ <div class="flex justify-between items-center gap-2 p-2 border-b h-10 shrink-0">
48
45
  <div class="flex items-center gap-1">
49
46
  <SidebarTrigger />
50
47
  </div>
51
48
  <div>
52
- <Button
53
- class="h-7 px-2 font-normal text-xs"
54
- Icon={Save}
55
- onclick={handleSave}
56
- >
49
+ <Button class="h-7 px-2 font-normal text-xs" Icon={Save} onclick={handleSave}>
57
50
  Save
58
51
  </Button>
59
52
  </div>
60
53
  </div>
61
54
 
62
55
  {#if loading}
63
- <div class="flex flex-col gap-2 p-2 w-full">
56
+ <div class="flex flex-col gap-2 p-4 w-full">
64
57
  <Skeleton class="h-8 w-full" />
65
58
  <Skeleton class="h-8 w-[80%]" />
66
59
  <Skeleton class="h-8 w-[60%]" />
67
60
  </div>
68
61
  {:else}
69
- <DetailViewForm bind:value={entry} fields={formFields} />
62
+ <div class="flex-1 overflow-y-auto max-w-xl">
63
+ <DetailViewForm bind:value={entry} fields={formFields} />
64
+ </div>
70
65
  {/if}
71
66
  </div>
@@ -73,7 +73,7 @@ export interface ExtensionProps {
73
73
  utils: ExtensionUtils;
74
74
  [key: string]: any;
75
75
  }
76
- export type ExtensionComponentKey = `pages.${string}` | "studio.listView" | `dvFields.topRight.${string}.${string}` | `detailView.update.subRecords.${string}` | `detailView.create.subRecords.${string}` | `detailView.fields.topRight.${string}.${string}` | `detailView.fields.foreignKey.${string}` | `listView.entry.children.${string}` | `listView.entry.actions`;
76
+ export type ExtensionComponentKey = `pages.${string}` | "studio.listView" | `dvFields.topRight.${string}.${string}` | `detailView.update.subRecords.${string}` | `detailView.create.subRecords.${string}` | `detailView.fields.topRight.${string}.${string}` | `detailView.fields.foreignKey.${string}` | `listView.entry.children.${string}` | `listView.entry.actions` | `listView.header.actions`;
77
77
  export type ExtensionComponent = any | {
78
78
  component: any;
79
79
  when: (props: Record<string, any>) => boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
3
  "license": "UNLICENSED",
4
- "version": "0.13.0",
4
+ "version": "0.14.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -18,6 +18,7 @@
18
18
  import ExtensionsComponents from "../extensionsComponents.svelte";
19
19
  import { getExtensionUtils, loadExtensionComponents } from "../../extensions/extensionUtils";
20
20
  import Tabs from "./dataTableTabs.svelte";
21
+ import { fade } from "svelte/transition";
21
22
 
22
23
  const { lobb, ctx } = getStudioContext();
23
24
 
@@ -84,6 +85,7 @@
84
85
  });
85
86
 
86
87
  async function loadData(params: any) {
88
+ loading = true;
87
89
  // parsing sort before sending the request
88
90
  const paramsCopy = $state.snapshot(params);
89
91
  const sort: TableProps["sort"] = paramsCopy.sort;
@@ -154,7 +156,6 @@
154
156
  />
155
157
  {/snippet}
156
158
 
157
- <Tabs {collectionName} {filter} bind:activeTabFilter />
158
159
  {#if showHeader}
159
160
  <Header bind:params {collectionName} bind:selectedRecords>
160
161
  {#snippet left()}
@@ -162,83 +163,88 @@
162
163
  {/snippet}
163
164
  </Header>
164
165
  {/if}
166
+ <Tabs {collectionName} {filter} bind:activeTabFilter />
165
167
  <div class="relative flex-1 overflow-auto w-full">
166
- {#if loading}
167
- <div class="flex flex-col gap-2 p-2 w-full">
168
- <Skeleton class="h-8 w-full" />
169
- <Skeleton class="h-8 w-[80%]" />
170
- <Skeleton class="h-8 w-[60%]" />
171
- </div>
172
- {:else}
173
- <Table
174
- {data}
175
- {columns}
176
- showCollapsible={doesCollectionHasChildren}
177
- selectByColumn="id"
178
- showLastRowBorder={true}
179
- showLastColumnBorder={true}
180
- bind:sort={params.sort}
181
- bind:selectedRecords
182
- {unifiedBgColor}
183
- bind:tableWidth={dataTableWidth}
184
- {...tableProps}
185
- rowActions={hasRowActions ? rowActionsSnippet : undefined}>
186
- {#snippet tools(entry)}
187
- <UpdateDetailViewButton
188
- {collectionName}
189
- recordId={entry.id}
190
- variant="ghost"
191
- class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
192
- Icon={Pencil}
193
- onSuccessfullSave={async () => {
194
- params = { ...params };
195
- }}
196
- ></UpdateDetailViewButton>
197
- {#if showDelete}
198
- <Button
199
- class="h-6 w-6 text-muted-foreground hover:bg-transparent"
200
- variant="ghost"
201
- size="icon"
202
- onclick={() => handleDelete(entry.id)}
203
- Icon={Trash}
204
- ></Button>
205
- {/if}
206
- {#await getWorkflowTools($state.snapshot(entry))}
207
- <div></div>
208
- {:then workflowTools}
209
- {#each workflowTools as workflowTool}
210
- <Button
168
+ {#key activeTabFilter}
169
+ <div class="h-full w-full" in:fade={{ duration: 120 }}>
170
+ {#if loading}
171
+ <div class="flex flex-col gap-2 p-2 w-full">
172
+ <Skeleton class="h-8 w-full" />
173
+ <Skeleton class="h-8 w-[80%]" />
174
+ <Skeleton class="h-8 w-[60%]" />
175
+ </div>
176
+ {:else}
177
+ <Table
178
+ {data}
179
+ {columns}
180
+ showCollapsible={doesCollectionHasChildren}
181
+ selectByColumn="id"
182
+ showLastRowBorder={true}
183
+ showLastColumnBorder={true}
184
+ bind:sort={params.sort}
185
+ bind:selectedRecords
186
+ {unifiedBgColor}
187
+ bind:tableWidth={dataTableWidth}
188
+ {...tableProps}
189
+ rowActions={hasRowActions ? rowActionsSnippet : undefined}>
190
+ {#snippet tools(entry)}
191
+ <UpdateDetailViewButton
192
+ {collectionName}
193
+ recordId={entry.id}
211
194
  variant="ghost"
212
195
  class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
213
- Icon={icons[
214
- workflowTool.icon as keyof typeof icons
215
- ]}
216
- onclick={workflowTool.onclick}
217
- ></Button>
218
- {/each}
219
- {/await}
220
- {/snippet}
221
- {#snippet overrideCell(value, column, entry)}
222
- <FieldCell
223
- {collectionName}
224
- fieldName={column.id}
225
- {value}
226
- {entry}
227
- tableParams={params}
228
- />
229
- {/snippet}
230
- {#snippet collapsible(entry)}
231
- <ChildRecords
232
- {collectionName}
233
- recordId={entry.id}
234
- width={dataTableWidth > dataTableContainerWidth
235
- ? dataTableContainerWidth
236
- : dataTableWidth}
237
- unifiedBgColor={unifiedBgColor ?? "bg-background"}
238
- />
239
- {/snippet}
240
- </Table>
241
- {/if}
196
+ Icon={Pencil}
197
+ onSuccessfullSave={async () => {
198
+ params = { ...params };
199
+ }}
200
+ ></UpdateDetailViewButton>
201
+ {#if showDelete}
202
+ <Button
203
+ class="h-6 w-6 text-muted-foreground hover:bg-transparent"
204
+ variant="ghost"
205
+ size="icon"
206
+ onclick={() => handleDelete(entry.id)}
207
+ Icon={Trash}
208
+ ></Button>
209
+ {/if}
210
+ {#await getWorkflowTools($state.snapshot(entry))}
211
+ <div></div>
212
+ {:then workflowTools}
213
+ {#each workflowTools as workflowTool}
214
+ <Button
215
+ variant="ghost"
216
+ class="h-5 w-5 px-0 py-0 text-muted-foreground hover:bg-transparent"
217
+ Icon={icons[
218
+ workflowTool.icon as keyof typeof icons
219
+ ]}
220
+ onclick={workflowTool.onclick}
221
+ ></Button>
222
+ {/each}
223
+ {/await}
224
+ {/snippet}
225
+ {#snippet overrideCell(value, column, entry)}
226
+ <FieldCell
227
+ {collectionName}
228
+ fieldName={column.id}
229
+ {value}
230
+ {entry}
231
+ tableParams={params}
232
+ />
233
+ {/snippet}
234
+ {#snippet collapsible(entry)}
235
+ <ChildRecords
236
+ {collectionName}
237
+ recordId={entry.id}
238
+ width={dataTableWidth > dataTableContainerWidth
239
+ ? dataTableContainerWidth
240
+ : dataTableWidth}
241
+ unifiedBgColor={unifiedBgColor ?? "bg-background"}
242
+ />
243
+ {/snippet}
244
+ </Table>
245
+ {/if}
246
+ </div>
247
+ {/key}
242
248
  </div>
243
249
  {#if showFooter}
244
250
  <Footer
@@ -10,6 +10,8 @@
10
10
  import CreateManyButton from "../createManyButton.svelte";
11
11
  import { showDialog } from "../confirmationDialog/store.svelte";
12
12
  import CreateDetailViewButton from "../detailView/create/createDetailViewButton.svelte";
13
+ import ExtensionsComponents from "../extensionsComponents.svelte";
14
+ import { getExtensionUtils } from "../../extensions/extensionUtils";
13
15
  import type { Snippet } from "svelte";
14
16
 
15
17
  interface Props {
@@ -142,6 +144,12 @@
142
144
  Icon={SquareStack}
143
145
  onSuccessfullSave={() => (params = { ...params })}
144
146
  ></CreateManyButton>
147
+ <ExtensionsComponents
148
+ name="listView.header.actions"
149
+ utils={getExtensionUtils(lobb, ctx)}
150
+ {collectionName}
151
+ refresh={() => { params = { ...params }; }}
152
+ />
145
153
  <CreateDetailViewButton
146
154
  {collectionName}
147
155
  variant="default"
@@ -6,30 +6,27 @@
6
6
  import Button from "./ui/button/button.svelte";
7
7
  import { onMount } from "svelte";
8
8
  import { getStudioContext } from "../context";
9
+ import { toast } from "svelte-sonner";
10
+ import Skeleton from "./ui/skeleton/skeleton.svelte";
9
11
 
10
12
  const { lobb, ctx } = getStudioContext();
11
- import { toast } from "svelte-sonner";
12
- import Skeleton from "./ui/skeleton/skeleton.svelte";
13
13
 
14
14
  interface Props {
15
15
  collectionName: string;
16
16
  }
17
17
 
18
- let {
19
- collectionName,
20
- }: Props = $props();
18
+ let { collectionName }: Props = $props();
21
19
 
22
20
  let entry = $state({});
23
21
  let loading = $state(true);
24
- let singletonExists = $state(true);
25
22
  const formFields = getCollectionFields(ctx, collectionName);
26
23
 
27
24
  onMount(async () => {
28
- const result = await lobb.readSingleton(collectionName);
29
- const json = await result.json();
30
- if (result.status === 404) {
31
- singletonExists = false;
32
- } else {
25
+ const result = await fetch(`${lobb.lobbUrl}/api/collections/${collectionName}/singleton`, {
26
+ headers: lobb.getHeaders() as HeadersInit,
27
+ });
28
+ if (result.status !== 404) {
29
+ const json = await result.json();
33
30
  entry = json.data;
34
31
  }
35
32
  loading = false;
@@ -43,29 +40,27 @@
43
40
  }
44
41
  </script>
45
42
 
46
- <div>
47
- <div class="flex justify-between items-center gap-2 p-2 border-b bg-background h-10">
43
+ <div class="flex flex-col h-full bg-background">
44
+ <div class="flex justify-between items-center gap-2 p-2 border-b h-10 shrink-0">
48
45
  <div class="flex items-center gap-1">
49
46
  <SidebarTrigger />
50
47
  </div>
51
48
  <div>
52
- <Button
53
- class="h-7 px-2 font-normal text-xs"
54
- Icon={Save}
55
- onclick={handleSave}
56
- >
49
+ <Button class="h-7 px-2 font-normal text-xs" Icon={Save} onclick={handleSave}>
57
50
  Save
58
51
  </Button>
59
52
  </div>
60
53
  </div>
61
54
 
62
55
  {#if loading}
63
- <div class="flex flex-col gap-2 p-2 w-full">
56
+ <div class="flex flex-col gap-2 p-4 w-full">
64
57
  <Skeleton class="h-8 w-full" />
65
58
  <Skeleton class="h-8 w-[80%]" />
66
59
  <Skeleton class="h-8 w-[60%]" />
67
60
  </div>
68
61
  {:else}
69
- <DetailViewForm bind:value={entry} fields={formFields} />
62
+ <div class="flex-1 overflow-y-auto max-w-xl">
63
+ <DetailViewForm bind:value={entry} fields={formFields} />
64
+ </div>
70
65
  {/if}
71
66
  </div>
@@ -93,7 +93,8 @@ export type ExtensionComponentKey =
93
93
  | `detailView.fields.topRight.${string}.${string}`
94
94
  | `detailView.fields.foreignKey.${string}`
95
95
  | `listView.entry.children.${string}`
96
- | `listView.entry.actions`;
96
+ | `listView.entry.actions`
97
+ | `listView.header.actions`;
97
98
 
98
99
  export type ExtensionComponent =
99
100
  | any