@lobb-js/studio 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/lib/Lobb.d.ts +30 -0
- package/src/lib/Lobb.ts +241 -0
- package/src/lib/components/Studio.svelte +5 -5
- package/src/lib/components/routes/collections/collection.svelte +46 -0
- package/src/lib/components/routes/collections/collections.svelte +43 -0
- package/src/lib/components/routes/collections/collections.svelte.d.ts +5 -0
- package/src/lib/components/routes/data_model/dataModel.svelte +40 -0
- package/src/lib/components/routes/data_model/dataModel.svelte.d.ts +3 -0
- package/src/lib/components/routes/data_model/flow.css +22 -0
- package/src/lib/components/routes/data_model/flow.svelte +82 -0
- package/src/lib/components/routes/data_model/flow.svelte.d.ts +5 -0
- package/src/lib/components/routes/data_model/syncManager.svelte +93 -0
- package/src/lib/components/routes/data_model/syncManager.svelte.d.ts +3 -0
- package/src/lib/components/routes/data_model/utils.d.ts +4 -0
- package/src/lib/components/routes/data_model/utils.ts +35 -0
- package/src/lib/components/routes/extensions/extension.svelte +16 -0
- package/src/lib/components/routes/home.svelte +36 -0
- package/src/lib/components/routes/workflows/workflows.svelte +135 -0
- package/src/lib/components/routes/workflows/workflows.svelte.d.ts +5 -0
- package/src/lib/eventSystem.d.ts +1 -0
- package/src/lib/eventSystem.ts +38 -0
- package/src/lib/extensions/extension.types.d.ts +83 -0
- package/src/lib/extensions/extension.types.ts +93 -0
- package/src/lib/extensions/extensionUtils.d.ts +8 -0
- package/src/lib/extensions/extensionUtils.ts +192 -0
- package/src/lib/index.d.ts +9 -0
- package/src/lib/index.ts +36 -0
- package/src/lib/store.svelte.d.ts +4 -0
- package/src/lib/store.svelte.ts +33 -0
- package/src/lib/store.types.d.ts +26 -0
- package/src/lib/store.types.ts +28 -0
- package/src/lib/utils.d.ts +27 -0
- package/src/lib/utils.ts +84 -0
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface RouteParams {
|
|
2
|
+
method: string;
|
|
3
|
+
route: string;
|
|
4
|
+
payload?: any;
|
|
5
|
+
}
|
|
6
|
+
type OnResponseHandlers = (reponse: Response) => void;
|
|
7
|
+
export declare class Lobb {
|
|
8
|
+
lobbUrl: string;
|
|
9
|
+
private headers;
|
|
10
|
+
private onResponseHandlers;
|
|
11
|
+
constructor(lobbUrl: string);
|
|
12
|
+
onResponse(callback: OnResponseHandlers): Promise<void>;
|
|
13
|
+
setHeaders(headers: HeadersInit): void;
|
|
14
|
+
getHeaders(): HeadersInit;
|
|
15
|
+
getMeta(): Promise<any>;
|
|
16
|
+
findAll(collectionName: string, params: any): Promise<Response>;
|
|
17
|
+
findOne(collectionName: string, id: string, queryParams?: any): Promise<Response>;
|
|
18
|
+
createOne(collectionName: string, body: any, file?: File): Promise<Response>;
|
|
19
|
+
updateOne(collectionName: string, id: string, body: any): Promise<Response>;
|
|
20
|
+
readSingleton(collectionName: string): Promise<Response>;
|
|
21
|
+
updateSingleton(collectionName: string, body: Record<string, any>): Promise<Response>;
|
|
22
|
+
deleteOne(collectionName: string, id: string, force?: boolean): Promise<Response>;
|
|
23
|
+
deleteMany(collectionName: string, filter: any, force?: boolean): Promise<Response>;
|
|
24
|
+
createMany(collectionName: string, body: any): Promise<Response>;
|
|
25
|
+
updateMany(collectionName: string, body: any, filter: any): Promise<Response>;
|
|
26
|
+
transactions(body: any[], rollback?: boolean): Promise<Response>;
|
|
27
|
+
request(params: RouteParams): Promise<Response>;
|
|
28
|
+
private handleResponse;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
package/src/lib/Lobb.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import qs from "qs";
|
|
2
|
+
import { parseFunction } from "./utils";
|
|
3
|
+
|
|
4
|
+
interface RouteParams {
|
|
5
|
+
method: string;
|
|
6
|
+
route: string;
|
|
7
|
+
payload?: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type OnResponseHandlers = (reponse: Response) => void;
|
|
11
|
+
|
|
12
|
+
export class Lobb {
|
|
13
|
+
public lobbUrl: string;
|
|
14
|
+
private headers: HeadersInit = [];
|
|
15
|
+
private onResponseHandlers: Array<OnResponseHandlers> = [];
|
|
16
|
+
|
|
17
|
+
constructor(lobbUrl: string) {
|
|
18
|
+
this.lobbUrl = lobbUrl;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async onResponse(callback: OnResponseHandlers) {
|
|
22
|
+
this.onResponseHandlers.push(callback);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public setHeaders(headers: HeadersInit) {
|
|
26
|
+
this.headers = headers;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public getHeaders() {
|
|
30
|
+
return this.headers;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async getMeta() {
|
|
34
|
+
const response = await fetch(`${this.lobbUrl}/api/meta`, {
|
|
35
|
+
headers: this.headers,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const metaResponse = await this.handleResponse(response);
|
|
39
|
+
const meta = await metaResponse.json();
|
|
40
|
+
|
|
41
|
+
meta.studio_workflows = meta.studio_workflows.map((workflow: any) => {
|
|
42
|
+
return {
|
|
43
|
+
...workflow,
|
|
44
|
+
handler: parseFunction(workflow.handler),
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return meta;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// CRUD methods
|
|
52
|
+
public async findAll(collectionName: string, params: any) {
|
|
53
|
+
const response = await fetch(
|
|
54
|
+
`${this.lobbUrl}/api/collections/${collectionName}/search`,
|
|
55
|
+
{
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: this.headers,
|
|
58
|
+
body: JSON.stringify(params),
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
return await this.handleResponse(response);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public async findOne(
|
|
65
|
+
collectionName: string,
|
|
66
|
+
id: string,
|
|
67
|
+
queryParams?: any,
|
|
68
|
+
) {
|
|
69
|
+
queryParams = queryParams ? `?${qs.stringify(queryParams)}` : "";
|
|
70
|
+
const response = await fetch(
|
|
71
|
+
`${this.lobbUrl}/api/collections/${collectionName}/${id}${queryParams}`,
|
|
72
|
+
{
|
|
73
|
+
method: "GET",
|
|
74
|
+
headers: this.headers,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
return await this.handleResponse(response);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public async createOne(collectionName: string, body: any, file?: File) {
|
|
81
|
+
if (file) {
|
|
82
|
+
const formData = new FormData();
|
|
83
|
+
Object.keys(body).forEach((key) => {
|
|
84
|
+
formData.append(key, body[key]);
|
|
85
|
+
});
|
|
86
|
+
formData.append("file", file, file.name);
|
|
87
|
+
formData.append(
|
|
88
|
+
"payload",
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
data: body,
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
body = formData;
|
|
94
|
+
} else {
|
|
95
|
+
body = JSON.stringify({
|
|
96
|
+
data: body,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const response = await fetch(
|
|
100
|
+
`${this.lobbUrl}/api/collections/${collectionName}`,
|
|
101
|
+
{
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: this.headers,
|
|
104
|
+
body,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
return await this.handleResponse(response);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public async updateOne(collectionName: string, id: string, body: any) {
|
|
111
|
+
const response = await fetch(
|
|
112
|
+
`${this.lobbUrl}/api/collections/${collectionName}/${id}`,
|
|
113
|
+
{
|
|
114
|
+
method: "PATCH",
|
|
115
|
+
headers: this.headers,
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
data: body,
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
return await this.handleResponse(response);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public async readSingleton(
|
|
125
|
+
collectionName: string,
|
|
126
|
+
) {
|
|
127
|
+
const response = await fetch(
|
|
128
|
+
`${this.lobbUrl}/api/collections/${collectionName}/singleton`,
|
|
129
|
+
{
|
|
130
|
+
method: "GET",
|
|
131
|
+
headers: this.headers,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
return await this.handleResponse(response);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public async updateSingleton(
|
|
138
|
+
collectionName: string,
|
|
139
|
+
body: Record<string, any>
|
|
140
|
+
) {
|
|
141
|
+
const response = await fetch(
|
|
142
|
+
`${this.lobbUrl}/api/collections/${collectionName}/singleton`,
|
|
143
|
+
{
|
|
144
|
+
method: "PATCH",
|
|
145
|
+
headers: this.headers,
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
data: body,
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
return await this.handleResponse(response);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async deleteOne(collectionName: string, id: string, force = false) {
|
|
155
|
+
const response = await fetch(
|
|
156
|
+
`${this.lobbUrl}/api/collections/${collectionName}/${id}${force ? "?force" : ""}`,
|
|
157
|
+
{
|
|
158
|
+
method: "DELETE",
|
|
159
|
+
headers: this.headers,
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
return await this.handleResponse(response);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public async deleteMany(collectionName: string, filter: any, force = false) {
|
|
166
|
+
const response = await fetch(
|
|
167
|
+
`${this.lobbUrl}/api/collections/${collectionName}${force ? "?force" : ""}`,
|
|
168
|
+
{
|
|
169
|
+
method: "DELETE",
|
|
170
|
+
headers: this.headers,
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
filter,
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
return await this.handleResponse(response);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public async createMany(collectionName: string, body: any) {
|
|
180
|
+
body = JSON.stringify({
|
|
181
|
+
data: body,
|
|
182
|
+
});
|
|
183
|
+
const response = await fetch(
|
|
184
|
+
`${this.lobbUrl}/api/collections/${collectionName}`,
|
|
185
|
+
{
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: this.headers,
|
|
188
|
+
body,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
return await this.handleResponse(response);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public async updateMany(collectionName: string, body: any, filter: any) {
|
|
195
|
+
const response = await fetch(
|
|
196
|
+
`${this.lobbUrl}/api/collections/${collectionName}`,
|
|
197
|
+
{
|
|
198
|
+
method: "PATCH",
|
|
199
|
+
headers: this.headers,
|
|
200
|
+
body: JSON.stringify({
|
|
201
|
+
filter,
|
|
202
|
+
data: body,
|
|
203
|
+
}),
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
return await this.handleResponse(response);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public async transactions(body: any[], rollback?: boolean) {
|
|
210
|
+
const response = await fetch(
|
|
211
|
+
`${this.lobbUrl}/api/collections/transactions${
|
|
212
|
+
rollback ? "?rollback" : ""
|
|
213
|
+
}`,
|
|
214
|
+
{
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: this.headers,
|
|
217
|
+
body: JSON.stringify(body),
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
return await this.handleResponse(response);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// CUSTOM REQUEST methods
|
|
224
|
+
public async request(params: RouteParams) {
|
|
225
|
+
const response = await fetch(`${this.lobbUrl}${params.route}`, {
|
|
226
|
+
method: params.method,
|
|
227
|
+
headers: this.headers,
|
|
228
|
+
body: JSON.stringify(params.payload),
|
|
229
|
+
});
|
|
230
|
+
return await this.handleResponse(response);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// HELPER methods
|
|
234
|
+
private async handleResponse(response: Response): Promise<Response> {
|
|
235
|
+
for (let index = 0; index < this.onResponseHandlers.length; index++) {
|
|
236
|
+
const handler = this.onResponseHandlers[index];
|
|
237
|
+
handler(response.clone());
|
|
238
|
+
}
|
|
239
|
+
return response;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
loadExtensions,
|
|
17
17
|
} from "$lib/extensions/extensionUtils";
|
|
18
18
|
import { mediaQueries } from "$lib/utils";
|
|
19
|
-
import Home from "
|
|
20
|
-
import DataModel from "
|
|
21
|
-
import Collections from "
|
|
22
|
-
import Workflows from "
|
|
23
|
-
import Extension from "
|
|
19
|
+
import Home from "./routes/home.svelte";
|
|
20
|
+
import DataModel from "./routes/data_model/dataModel.svelte";
|
|
21
|
+
import Collections from "./routes/collections/collections.svelte";
|
|
22
|
+
import Workflows from "./routes/workflows/workflows.svelte";
|
|
23
|
+
import Extension from "./routes/extensions/extension.svelte";
|
|
24
24
|
|
|
25
25
|
interface StudioProps {
|
|
26
26
|
extensions?: any[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { CircleSlash2 } from "lucide-svelte";
|
|
3
|
+
import DataTable from "$lib/components/dataTable/dataTable.svelte";
|
|
4
|
+
import SidebarTrigger from "$lib/components/sidebar/sidebarTrigger.svelte";
|
|
5
|
+
import { ctx } from "$lib/store.svelte";
|
|
6
|
+
import Singletone from "$lib/components/singletone.svelte";
|
|
7
|
+
|
|
8
|
+
let { collectionName } = $props();
|
|
9
|
+
let isSingletonCollection = $derived(ctx.meta.collections[collectionName].singleton);
|
|
10
|
+
|
|
11
|
+
let containerWidth = $state();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div bind:clientWidth={containerWidth} class="h-full">
|
|
15
|
+
{#if collectionName}
|
|
16
|
+
{#if isSingletonCollection}
|
|
17
|
+
<Singletone collectionName={collectionName} />
|
|
18
|
+
{:else}
|
|
19
|
+
<DataTable
|
|
20
|
+
{collectionName}
|
|
21
|
+
tableProps={{
|
|
22
|
+
parentWidth: containerWidth,
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
{#snippet headerLeft()}
|
|
26
|
+
<SidebarTrigger />
|
|
27
|
+
{/snippet}
|
|
28
|
+
</DataTable>
|
|
29
|
+
{/if}
|
|
30
|
+
{:else}
|
|
31
|
+
<div
|
|
32
|
+
class="relative flex h-full w-full flex-col items-center justify-center gap-4 text-muted-foreground"
|
|
33
|
+
>
|
|
34
|
+
<CircleSlash2 class="opacity-50" size="50" />
|
|
35
|
+
<div class="flex flex-col items-center justify-center">
|
|
36
|
+
<div>No collection selected</div>
|
|
37
|
+
<div class="text-xs">
|
|
38
|
+
Select a collection to view its entries or create new ones
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="absolute top-0 left-0 p-2.5">
|
|
42
|
+
<SidebarTrigger />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SideBarData } from "$lib/components/sidebar/sidebarElements.svelte";
|
|
3
|
+
import Sidebar from "$lib/components/sidebar/sidebar.svelte";
|
|
4
|
+
import { ctx } from "$lib/store.svelte";
|
|
5
|
+
import Collection from "./collection.svelte";
|
|
6
|
+
import { Table } from "lucide-svelte";
|
|
7
|
+
|
|
8
|
+
let { collectionName } = $props();
|
|
9
|
+
|
|
10
|
+
const collectionsList = $state(getCollectionsList());
|
|
11
|
+
|
|
12
|
+
function getCollectionsList() {
|
|
13
|
+
const collections = ctx.meta.collections;
|
|
14
|
+
let collectionsOwners: SideBarData = Object.entries(collections).map(
|
|
15
|
+
([collectionName, collectionValue]) => {
|
|
16
|
+
return {
|
|
17
|
+
name: collectionName,
|
|
18
|
+
path: collectionValue.category ?? collectionValue.owner,
|
|
19
|
+
icon: Table,
|
|
20
|
+
href: `/collections/${collectionName}`,
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// updating the path from '__project' and '__core' to a more readable names
|
|
26
|
+
collectionsOwners = collectionsOwners.map((item) => {
|
|
27
|
+
if (item.path === "__project") {
|
|
28
|
+
item.path = "project";
|
|
29
|
+
} else if (item.path === "__core") {
|
|
30
|
+
item.path = "core";
|
|
31
|
+
}
|
|
32
|
+
return item;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return collectionsOwners;
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<Sidebar title="Collections" data={collectionsList}>
|
|
40
|
+
{#key collectionName}
|
|
41
|
+
<Collection {collectionName} />
|
|
42
|
+
{/key}
|
|
43
|
+
</Sidebar>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { SvelteFlowProvider } from "@xyflow/svelte";
|
|
3
|
+
import Flow from "./flow.svelte";
|
|
4
|
+
import Sidebar from "$lib/components/sidebar/sidebar.svelte";
|
|
5
|
+
import { location } from "@wjfe/n-savant";
|
|
6
|
+
import SyncManager from "./syncManager.svelte";
|
|
7
|
+
import SidebarTrigger from "$lib/components/sidebar/sidebarTrigger.svelte";
|
|
8
|
+
|
|
9
|
+
const currentPage = $derived(location.url.pathname.split("/")[2]);
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<Sidebar
|
|
13
|
+
title="Data Model"
|
|
14
|
+
showSearch={false}
|
|
15
|
+
data={[
|
|
16
|
+
{
|
|
17
|
+
name: "graph",
|
|
18
|
+
href: "/datamodel/graph",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "query_editor",
|
|
22
|
+
href: "/datamodel/query_editor",
|
|
23
|
+
},
|
|
24
|
+
]}
|
|
25
|
+
>
|
|
26
|
+
<div class="relative h-full w-full">
|
|
27
|
+
{#if currentPage === "graph"}
|
|
28
|
+
<SvelteFlowProvider>
|
|
29
|
+
<div style:width="100%" style:height="100%">
|
|
30
|
+
<Flow />
|
|
31
|
+
</div>
|
|
32
|
+
</SvelteFlowProvider>
|
|
33
|
+
{:else if currentPage === "query_editor"}
|
|
34
|
+
<SyncManager />
|
|
35
|
+
{/if}
|
|
36
|
+
<div class="absolute top-0 left-0 p-2.5">
|
|
37
|
+
<SidebarTrigger />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</Sidebar>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.svelte-flow {
|
|
2
|
+
--xy-edge-stroke-default: hsl(var(--muted-foreground));
|
|
3
|
+
--xy-edge-stroke-selected-default: hsl(var(--primary));
|
|
4
|
+
--xy-connectionline-stroke-default: hsl(var(--primary));
|
|
5
|
+
--xy-attribution-background-color-default: transparent;
|
|
6
|
+
--xy-minimap-background-color-default: hsl(var(--background));
|
|
7
|
+
--xy-minimap-mask-background-color-default: hsl(var(--primary) / 0.1);
|
|
8
|
+
--xy-minimap-node-background-color-default: hsl(var(--primary) / 0.2);
|
|
9
|
+
--xy-background-color-default: hsl(var(--soft));
|
|
10
|
+
--xy-background-pattern-dots-color-default: hsl(var(--muted-foreground));
|
|
11
|
+
--xy-background-pattern-lines-color-default: hsl(var(--background));
|
|
12
|
+
--xy-background-pattern-cross-color-default: hsl(var(--soft));
|
|
13
|
+
--xy-node-border-default: 1px solid hsl(var(--primary) / 0.25);
|
|
14
|
+
--xy-node-background-color-default: hsl(var(--background));
|
|
15
|
+
--xy-node-boxshadow-selected-default: 0 0 0 0.5px hsl(var(--primary) / 0.5);
|
|
16
|
+
--xy-handle-background-color-default: hsl(var(--primary));
|
|
17
|
+
--xy-handle-border-color-default: hsl(var(--background));
|
|
18
|
+
--xy-controls-button-background-color-default: hsl(var(--background));
|
|
19
|
+
--xy-controls-button-background-color-hover-default: hsl(var(--muted));
|
|
20
|
+
--xy-controls-button-border-color-default: hsl(var(--muted));
|
|
21
|
+
--xy-edge-label-background-color-default: hsl(var(--background));
|
|
22
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import "@xyflow/svelte/dist/style.css";
|
|
3
|
+
import "./flow.css";
|
|
4
|
+
|
|
5
|
+
import type { Node, Edge } from "@xyflow/svelte";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
SvelteFlow,
|
|
9
|
+
Background,
|
|
10
|
+
MiniMap,
|
|
11
|
+
Controls,
|
|
12
|
+
useSvelteFlow,
|
|
13
|
+
} from "@xyflow/svelte";
|
|
14
|
+
import { getLayoutedElements } from "./utils";
|
|
15
|
+
import { onMount } from "svelte";
|
|
16
|
+
import { ctx } from "$lib/store.svelte";
|
|
17
|
+
|
|
18
|
+
const { fitView } = useSvelteFlow();
|
|
19
|
+
|
|
20
|
+
let nodes = $state.raw<Node[]>(generateNodes());
|
|
21
|
+
let edges = $state.raw<Edge[]>(generateEdges());
|
|
22
|
+
|
|
23
|
+
onMount(() => {
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
onLayout();
|
|
26
|
+
}, 0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function generateNodes() {
|
|
30
|
+
const localNodes: Node[] = [];
|
|
31
|
+
|
|
32
|
+
for (const [collectionName, collectionValue] of Object.entries(
|
|
33
|
+
ctx.meta.collections,
|
|
34
|
+
)) {
|
|
35
|
+
if (collectionValue.owner !== "__project") {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
localNodes.push({
|
|
40
|
+
id: collectionName,
|
|
41
|
+
data: { label: collectionName },
|
|
42
|
+
position: { x: 0, y: 0 },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return localNodes;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function generateEdges() {
|
|
50
|
+
const localEdges: Edge[] = [];
|
|
51
|
+
|
|
52
|
+
const relations = ctx.meta.relations;
|
|
53
|
+
for (let index = 0; index < relations.length; index++) {
|
|
54
|
+
const relation = relations[index];
|
|
55
|
+
localEdges.push({
|
|
56
|
+
id: `${relation.from.collection}_${relation.to.collection}`,
|
|
57
|
+
source: relation.from.collection,
|
|
58
|
+
target: relation.to.collection,
|
|
59
|
+
animated: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return localEdges;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function onLayout() {
|
|
67
|
+
const layouted = getLayoutedElements(nodes, edges);
|
|
68
|
+
|
|
69
|
+
nodes = [...layouted.nodes];
|
|
70
|
+
edges = [...layouted.edges];
|
|
71
|
+
|
|
72
|
+
fitView({
|
|
73
|
+
padding: 0.5,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<SvelteFlow bind:nodes bind:edges>
|
|
79
|
+
<Background />
|
|
80
|
+
<MiniMap />
|
|
81
|
+
<Controls />
|
|
82
|
+
</SvelteFlow>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import DiffViewer from "$lib/components/diffViewer.svelte";
|
|
3
|
+
import { ctx } from "$lib/store.svelte";
|
|
4
|
+
import { onMount } from "svelte";
|
|
5
|
+
import stringify from "json-stable-stringify";
|
|
6
|
+
import MonacoEditor from "$lib/components/monacoEditor.svelte";
|
|
7
|
+
import Table from "$lib/components/dataTable/table.svelte";
|
|
8
|
+
import Button from "$lib/components/ui/button/button.svelte";
|
|
9
|
+
import { LoaderCircle, SendHorizontal } from "lucide-svelte";
|
|
10
|
+
import { lobb } from "$lib";
|
|
11
|
+
|
|
12
|
+
let configSchema: string = $state("");
|
|
13
|
+
let dbSchema: string = $state("");
|
|
14
|
+
let sqlPrompt = $state("");
|
|
15
|
+
let sqlResult = $state([]);
|
|
16
|
+
|
|
17
|
+
onMount(() => {
|
|
18
|
+
loadSchemas();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function loadSchemas() {
|
|
22
|
+
configSchema = "";
|
|
23
|
+
dbSchema = "";
|
|
24
|
+
const response = await fetch(`${ctx.lobbUrl}/api/schema/diff`);
|
|
25
|
+
const result = await response.json();
|
|
26
|
+
configSchema = stringify(result.dbSchema, {
|
|
27
|
+
space: 2,
|
|
28
|
+
}) as string;
|
|
29
|
+
dbSchema = stringify(result.configSchema, {
|
|
30
|
+
space: 2,
|
|
31
|
+
}) as string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function handleExecute() {
|
|
35
|
+
const response = await lobb.createOne("core_query", {
|
|
36
|
+
query: sqlPrompt,
|
|
37
|
+
});
|
|
38
|
+
const result = await response.json();
|
|
39
|
+
sqlResult = result.data;
|
|
40
|
+
loadSchemas();
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div class="h-[50%] border-b">
|
|
45
|
+
{#if configSchema && dbSchema}
|
|
46
|
+
<DiffViewer
|
|
47
|
+
type="json"
|
|
48
|
+
original={configSchema}
|
|
49
|
+
modified={dbSchema}
|
|
50
|
+
class="h-full rounded-none border-0"
|
|
51
|
+
/>
|
|
52
|
+
{:else}
|
|
53
|
+
<div class="flex justify-center items-center h-full gap-2">
|
|
54
|
+
<LoaderCircle class="animate-spin" />
|
|
55
|
+
<div>loading...</div>
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
</div>
|
|
59
|
+
<div class="flex h-[50%] w-full">
|
|
60
|
+
<div class="h-full flex-1 flex flex-col border-r">
|
|
61
|
+
<div
|
|
62
|
+
class="h-10 flex items-center px-2 bg-background border-b justify-between"
|
|
63
|
+
>
|
|
64
|
+
<div>Query Editor</div>
|
|
65
|
+
<Button
|
|
66
|
+
class="h-7 px-3 text-xs font-normal"
|
|
67
|
+
Icon={SendHorizontal}
|
|
68
|
+
onclick={handleExecute}
|
|
69
|
+
>
|
|
70
|
+
Execute
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
<MonacoEditor
|
|
74
|
+
type="sql"
|
|
75
|
+
name="prompt"
|
|
76
|
+
bind:value={sqlPrompt}
|
|
77
|
+
class="flex-1 rounded-none border-0"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="flex-1">
|
|
81
|
+
{#if Array.isArray(sqlResult) && sqlResult.length}
|
|
82
|
+
<Table
|
|
83
|
+
data={sqlResult}
|
|
84
|
+
showLastRowBorder={true}
|
|
85
|
+
showLastColumnBorder={true}
|
|
86
|
+
/>
|
|
87
|
+
{:else}
|
|
88
|
+
<div class="flex flex-1 h-full items-center justify-center">
|
|
89
|
+
No results
|
|
90
|
+
</div>
|
|
91
|
+
{/if}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|