@lobb-js/studio 0.1.35 → 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/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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function emitEvent(eventName: string, input: Record<string, any>): Promise<Record<string, any>>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { toast } from "svelte-sonner";
|
|
2
|
+
import { ctx } from "./store.svelte";
|
|
3
|
+
import { openCreateDetailView, openUpdateDetailView } from "./components/detailView/store.svelte";
|
|
4
|
+
|
|
5
|
+
export async function emitEvent(eventName: string, input: Record<string, any>) {
|
|
6
|
+
const workflows = ctx.meta.studio_workflows.filter(
|
|
7
|
+
(workflow) => {
|
|
8
|
+
return eventName.startsWith(workflow.eventName);
|
|
9
|
+
},
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
for (let index = 0; index < workflows.length; index++) {
|
|
13
|
+
const workflow = workflows[index];
|
|
14
|
+
try {
|
|
15
|
+
const localOutput = await workflow.handler(
|
|
16
|
+
input,
|
|
17
|
+
await getEventContext(),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (localOutput) {
|
|
21
|
+
input = localOutput;
|
|
22
|
+
}
|
|
23
|
+
} catch (error) {
|
|
24
|
+
toast.error((error as any).message);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getEventContext() {
|
|
33
|
+
return {
|
|
34
|
+
toast,
|
|
35
|
+
openCreateDetailView,
|
|
36
|
+
openUpdateDetailView,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Lobb } from "$lib/Lobb";
|
|
2
|
+
import type { CTX } from "../lib/store.types";
|
|
3
|
+
import type { Button } from "$lib/components/ui/button";
|
|
4
|
+
import type { Input } from "$lib/components/ui/input";
|
|
5
|
+
import type { Separator } from "$lib/components/ui/separator";
|
|
6
|
+
import type { Skeleton } from "$lib/components/ui/skeleton";
|
|
7
|
+
import type LlmButton from "$lib/components/LlmButton.svelte";
|
|
8
|
+
import type Sidebar from "$lib/components/sidebar/sidebar.svelte";
|
|
9
|
+
import type SidebarTrigger from "$lib/components/sidebar/sidebarTrigger.svelte";
|
|
10
|
+
import type CreateDetailViewButton from "$lib/components/detailView/create/createDetailViewButton.svelte";
|
|
11
|
+
import type UpdateDetailViewButton from "$lib/components/detailView/update/updateDetailViewButton.svelte";
|
|
12
|
+
import type { getFileFromUser, mediaQueries } from "$lib/utils";
|
|
13
|
+
import type Table from "$lib/components/dataTable/table.svelte";
|
|
14
|
+
import type { Location } from "@wjfe/n-savant";
|
|
15
|
+
import type RangeCalendarButton from "$lib/components/rangeCalendarButton.svelte";
|
|
16
|
+
import type DataTable from "$lib/components/dataTable/dataTable.svelte";
|
|
17
|
+
import type Drawer from "$lib/components/drawer.svelte";
|
|
18
|
+
import type SelectRecord from "$lib/components/selectRecord.svelte";
|
|
19
|
+
import * as Popover from "$lib/components/ui/popover";
|
|
20
|
+
import * as intlDate from "@internationalized/date";
|
|
21
|
+
import * as Icons from "lucide-svelte";
|
|
22
|
+
import { ContextMenu } from "bits-ui";
|
|
23
|
+
import * as Tooltip from "$lib/components/ui/tooltip";
|
|
24
|
+
import * as Breadcrumb from "$lib/components/ui/breadcrumb";
|
|
25
|
+
import { showDialog } from "$lib/components/confirmationDialog/store.svelte";
|
|
26
|
+
import { toast } from "svelte-sonner";
|
|
27
|
+
import { Switch } from "$lib/components/ui/switch";
|
|
28
|
+
export interface Components {
|
|
29
|
+
Button: typeof Button;
|
|
30
|
+
Input: typeof Input;
|
|
31
|
+
Separator: typeof Separator;
|
|
32
|
+
Skeleton: typeof Skeleton;
|
|
33
|
+
LlmButton: typeof LlmButton;
|
|
34
|
+
Sidebar: typeof Sidebar;
|
|
35
|
+
SidebarTrigger: typeof SidebarTrigger;
|
|
36
|
+
CreateDetailViewButton: typeof CreateDetailViewButton;
|
|
37
|
+
UpdateDetailViewButton: typeof UpdateDetailViewButton;
|
|
38
|
+
Tooltip: typeof Tooltip;
|
|
39
|
+
Breadcrumb: typeof Breadcrumb;
|
|
40
|
+
ContextMenu: typeof ContextMenu;
|
|
41
|
+
Popover: typeof Popover;
|
|
42
|
+
Icons: typeof Icons;
|
|
43
|
+
Table: typeof Table;
|
|
44
|
+
RangeCalendarButton: typeof RangeCalendarButton;
|
|
45
|
+
DataTable: typeof DataTable;
|
|
46
|
+
Drawer: typeof Drawer;
|
|
47
|
+
SelectRecord: typeof SelectRecord;
|
|
48
|
+
Switch: typeof Switch;
|
|
49
|
+
}
|
|
50
|
+
export interface ExtensionUtils {
|
|
51
|
+
ctx: CTX;
|
|
52
|
+
lobb: Lobb;
|
|
53
|
+
location: Location;
|
|
54
|
+
toast: typeof toast;
|
|
55
|
+
showDialog: typeof showDialog;
|
|
56
|
+
getFileFromUser: typeof getFileFromUser;
|
|
57
|
+
components: Components;
|
|
58
|
+
mediaQueries: typeof mediaQueries;
|
|
59
|
+
intlDate: typeof intlDate;
|
|
60
|
+
}
|
|
61
|
+
interface DashboardNav {
|
|
62
|
+
label: string;
|
|
63
|
+
icon: any;
|
|
64
|
+
href?: string;
|
|
65
|
+
onclick?: () => void;
|
|
66
|
+
navs?: DashboardNav[];
|
|
67
|
+
}
|
|
68
|
+
export interface DashboardNavs {
|
|
69
|
+
top?: DashboardNav[];
|
|
70
|
+
middle?: DashboardNav[];
|
|
71
|
+
bottom?: DashboardNav[];
|
|
72
|
+
}
|
|
73
|
+
export interface ExtensionProps {
|
|
74
|
+
utils: ExtensionUtils;
|
|
75
|
+
[key: string]: any;
|
|
76
|
+
}
|
|
77
|
+
export interface Extension {
|
|
78
|
+
name: string;
|
|
79
|
+
onStartup?: (utils: ExtensionUtils) => Promise<void>;
|
|
80
|
+
components?: Record<string, any>;
|
|
81
|
+
dashboardNavs?: DashboardNavs;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Lobb } from "$lib/Lobb";
|
|
2
|
+
import type { CTX } from "../lib/store.types";
|
|
3
|
+
import type { Button } from "$lib/components/ui/button";
|
|
4
|
+
import type { Input } from "$lib/components/ui/input";
|
|
5
|
+
import type { Separator } from "$lib/components/ui/separator";
|
|
6
|
+
import type { Skeleton } from "$lib/components/ui/skeleton";
|
|
7
|
+
import type LlmButton from "$lib/components/LlmButton.svelte";
|
|
8
|
+
import type Sidebar from "$lib/components/sidebar/sidebar.svelte";
|
|
9
|
+
import type SidebarTrigger from "$lib/components/sidebar/sidebarTrigger.svelte";
|
|
10
|
+
import type CreateDetailViewButton from "$lib/components/detailView/create/createDetailViewButton.svelte";
|
|
11
|
+
import type UpdateDetailViewButton from "$lib/components/detailView/update/updateDetailViewButton.svelte";
|
|
12
|
+
import type { getFileFromUser, mediaQueries } from "$lib/utils";
|
|
13
|
+
import type Table from "$lib/components/dataTable/table.svelte";
|
|
14
|
+
import type { Location } from "@wjfe/n-savant";
|
|
15
|
+
import type RangeCalendarButton from "$lib/components/rangeCalendarButton.svelte";
|
|
16
|
+
import type DataTable from "$lib/components/dataTable/dataTable.svelte";
|
|
17
|
+
import type Drawer from "$lib/components/drawer.svelte";
|
|
18
|
+
import type SelectRecord from "$lib/components/selectRecord.svelte";
|
|
19
|
+
import * as Popover from "$lib/components/ui/popover";
|
|
20
|
+
import * as intlDate from "@internationalized/date";
|
|
21
|
+
import * as Icons from "lucide-svelte"
|
|
22
|
+
import { ContextMenu } from "bits-ui";
|
|
23
|
+
import * as Tooltip from "$lib/components/ui/tooltip";
|
|
24
|
+
import * as Breadcrumb from "$lib/components/ui/breadcrumb";
|
|
25
|
+
import { showDialog } from "$lib/components/confirmationDialog/store.svelte";
|
|
26
|
+
import { toast } from "svelte-sonner";
|
|
27
|
+
import type Drawer from "$lib/components/drawer.svelte";
|
|
28
|
+
import { Switch } from "$lib/components/ui/switch";
|
|
29
|
+
|
|
30
|
+
// extensions utils
|
|
31
|
+
export interface Components {
|
|
32
|
+
Button: typeof Button;
|
|
33
|
+
Input: typeof Input;
|
|
34
|
+
Separator: typeof Separator;
|
|
35
|
+
Skeleton: typeof Skeleton;
|
|
36
|
+
LlmButton: typeof LlmButton;
|
|
37
|
+
Sidebar: typeof Sidebar;
|
|
38
|
+
SidebarTrigger: typeof SidebarTrigger;
|
|
39
|
+
CreateDetailViewButton: typeof CreateDetailViewButton;
|
|
40
|
+
UpdateDetailViewButton: typeof UpdateDetailViewButton;
|
|
41
|
+
Tooltip: typeof Tooltip;
|
|
42
|
+
Breadcrumb: typeof Breadcrumb;
|
|
43
|
+
ContextMenu: typeof ContextMenu;
|
|
44
|
+
Popover: typeof Popover;
|
|
45
|
+
Icons: typeof Icons;
|
|
46
|
+
Table: typeof Table;
|
|
47
|
+
RangeCalendarButton: typeof RangeCalendarButton;
|
|
48
|
+
DataTable: typeof DataTable;
|
|
49
|
+
Drawer: typeof Drawer;
|
|
50
|
+
SelectRecord: typeof SelectRecord,
|
|
51
|
+
Switch: typeof Switch,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ExtensionUtils {
|
|
55
|
+
ctx: CTX;
|
|
56
|
+
lobb: Lobb;
|
|
57
|
+
location: Location;
|
|
58
|
+
toast: typeof toast;
|
|
59
|
+
showDialog: typeof showDialog;
|
|
60
|
+
getFileFromUser: typeof getFileFromUser;
|
|
61
|
+
components: Components;
|
|
62
|
+
mediaQueries: typeof mediaQueries;
|
|
63
|
+
intlDate: typeof intlDate;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// dashboard nav
|
|
67
|
+
interface DashboardNav {
|
|
68
|
+
label: string;
|
|
69
|
+
icon: any;
|
|
70
|
+
href?: string;
|
|
71
|
+
onclick?: () => void;
|
|
72
|
+
navs?: DashboardNav[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DashboardNavs {
|
|
76
|
+
top?: DashboardNav[];
|
|
77
|
+
middle?: DashboardNav[];
|
|
78
|
+
bottom?: DashboardNav[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// extension components base Props
|
|
82
|
+
export interface ExtensionProps {
|
|
83
|
+
utils: ExtensionUtils;
|
|
84
|
+
[key: string]: any;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// extension exported object
|
|
88
|
+
export interface Extension {
|
|
89
|
+
name: string;
|
|
90
|
+
onStartup?: (utils: ExtensionUtils) => Promise<void>;
|
|
91
|
+
components?: Record<string, any>;
|
|
92
|
+
dashboardNavs?: DashboardNavs;
|
|
93
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Components, DashboardNavs, Extension, ExtensionUtils } from "./extension.types";
|
|
2
|
+
export declare function getComponents(): Components;
|
|
3
|
+
export declare function getExtensionUtils(): ExtensionUtils;
|
|
4
|
+
export declare function getExtensionsThatHasDash(): string[];
|
|
5
|
+
export declare function loadExtensions(studioExtensions?: any[]): Promise<Record<string, Extension>>;
|
|
6
|
+
export declare function loadExtensionComponents(name: string, filterByExtensions?: string[]): any[];
|
|
7
|
+
export declare function executeExtensionsOnStartup(): Promise<void>;
|
|
8
|
+
export declare function getDashboardNavs(): DashboardNavs;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Components,
|
|
3
|
+
DashboardNavs,
|
|
4
|
+
Extension,
|
|
5
|
+
ExtensionUtils,
|
|
6
|
+
} from "./extension.types";
|
|
7
|
+
import { lobb } from "$lib";
|
|
8
|
+
import { ctx } from "$lib/store.svelte";
|
|
9
|
+
import { toast } from "svelte-sonner";
|
|
10
|
+
import { showDialog } from "$lib/components/confirmationDialog/store.svelte";
|
|
11
|
+
import { Button } from "$lib/components/ui/button";
|
|
12
|
+
import { Input } from "$lib/components/ui/input";
|
|
13
|
+
import { Separator } from "$lib/components/ui/separator";
|
|
14
|
+
import { Skeleton } from "$lib/components/ui/skeleton";
|
|
15
|
+
import Table from "$lib/components/dataTable/table.svelte";
|
|
16
|
+
import { getFileFromUser, mediaQueries } from "$lib/utils";
|
|
17
|
+
import LlmButton from "$lib/components/LlmButton.svelte";
|
|
18
|
+
import Sidebar from "$lib/components/sidebar/sidebar.svelte";
|
|
19
|
+
import SidebarTrigger from "$lib/components/sidebar/sidebarTrigger.svelte";
|
|
20
|
+
import CreateDetailViewButton from "$lib/components/detailView/create/createDetailViewButton.svelte";
|
|
21
|
+
import UpdateDetailViewButton from "$lib/components/detailView/update/updateDetailViewButton.svelte";
|
|
22
|
+
import * as intlDate from "@internationalized/date";
|
|
23
|
+
import * as Tooltip from "$lib/components/ui/tooltip";
|
|
24
|
+
import * as Breadcrumb from "$lib/components/ui/breadcrumb";
|
|
25
|
+
import { ContextMenu } from "bits-ui";
|
|
26
|
+
import * as Popover from "$lib/components/ui/popover";
|
|
27
|
+
import * as Icons from "lucide-svelte";
|
|
28
|
+
import { location } from "@wjfe/n-savant";
|
|
29
|
+
import RangeCalendarButton from "$lib/components/rangeCalendarButton.svelte";
|
|
30
|
+
import DataTable from "$lib/components/dataTable/dataTable.svelte";
|
|
31
|
+
import Drawer from "$lib/components/drawer.svelte";
|
|
32
|
+
import SelectRecord from "$lib/components/selectRecord.svelte";
|
|
33
|
+
import { Switch } from "$lib/components/ui/switch";
|
|
34
|
+
|
|
35
|
+
export function getComponents(): Components {
|
|
36
|
+
return {
|
|
37
|
+
Button: Button,
|
|
38
|
+
Input: Input,
|
|
39
|
+
Separator: Separator,
|
|
40
|
+
Skeleton: Skeleton,
|
|
41
|
+
LlmButton: LlmButton,
|
|
42
|
+
Sidebar: Sidebar,
|
|
43
|
+
SidebarTrigger: SidebarTrigger,
|
|
44
|
+
CreateDetailViewButton: CreateDetailViewButton,
|
|
45
|
+
UpdateDetailViewButton: UpdateDetailViewButton,
|
|
46
|
+
Tooltip: Tooltip,
|
|
47
|
+
Breadcrumb: Breadcrumb,
|
|
48
|
+
ContextMenu: ContextMenu,
|
|
49
|
+
Popover: Popover,
|
|
50
|
+
Icons: Icons,
|
|
51
|
+
Table: Table,
|
|
52
|
+
RangeCalendarButton: RangeCalendarButton,
|
|
53
|
+
DataTable: DataTable,
|
|
54
|
+
Drawer: Drawer,
|
|
55
|
+
SelectRecord: SelectRecord,
|
|
56
|
+
Switch: Switch,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getExtensionUtils(): ExtensionUtils {
|
|
61
|
+
return {
|
|
62
|
+
ctx: ctx,
|
|
63
|
+
lobb: lobb,
|
|
64
|
+
location: location,
|
|
65
|
+
toast: toast,
|
|
66
|
+
showDialog: showDialog,
|
|
67
|
+
getFileFromUser: getFileFromUser,
|
|
68
|
+
components: getComponents(),
|
|
69
|
+
mediaQueries: mediaQueries,
|
|
70
|
+
intlDate: intlDate,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getExtensionsThatHasDash(): string[] {
|
|
75
|
+
const extensionNames = Object.keys(ctx.meta.extensions);
|
|
76
|
+
|
|
77
|
+
const extensionsWithDashExt = [];
|
|
78
|
+
for (let index = 0; index < extensionNames.length; index++) {
|
|
79
|
+
const extensionName = extensionNames[index];
|
|
80
|
+
const extensionMeta = ctx.meta.extensions[extensionName];
|
|
81
|
+
if (extensionMeta.hasDashboardExtension) {
|
|
82
|
+
extensionsWithDashExt.push(extensionName);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return extensionsWithDashExt;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function loadExtensions(studioExtensions?: any[]): Promise<Record<string, Extension>> {
|
|
90
|
+
const extensions: Record<string, Extension> = {};
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if (studioExtensions) {
|
|
94
|
+
for (let index = 0; index < studioExtensions.length; index++) {
|
|
95
|
+
const studioExtension = studioExtensions[index](getExtensionUtils());
|
|
96
|
+
extensions[studioExtension.name] = studioExtension;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const extensionsThatHasDash = getExtensionsThatHasDash();
|
|
101
|
+
for (let index = 0; index < extensionsThatHasDash.length; index++) {
|
|
102
|
+
const extensionName = extensionsThatHasDash[index];
|
|
103
|
+
|
|
104
|
+
// Try to import locally-installed extension by name first
|
|
105
|
+
if (extensionName && extensions[extensionName]) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fall back to remote import
|
|
110
|
+
try {
|
|
111
|
+
extensions[extensionName] = (
|
|
112
|
+
await import(
|
|
113
|
+
/* @vite-ignore */ `${ctx.lobbUrl}/api/extensions/${extensionName}/dashboard?v=${ctx.meta.extensions[extensionName]?.version}`
|
|
114
|
+
)
|
|
115
|
+
).extension(getExtensionUtils());
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn(`Failed to load remote extension ${extensionName}`, error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return extensions;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function loadExtensionComponents(
|
|
125
|
+
name: string,
|
|
126
|
+
filterByExtensions?: string[],
|
|
127
|
+
): any[] {
|
|
128
|
+
const components = [];
|
|
129
|
+
for (const [extensionName, extensionValue] of Object.entries(
|
|
130
|
+
ctx.extensions,
|
|
131
|
+
)) {
|
|
132
|
+
if (filterByExtensions && !filterByExtensions.includes(extensionName)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (extensionValue.components) {
|
|
136
|
+
for (const [componentName, componentValue] of Object.entries(
|
|
137
|
+
extensionValue.components,
|
|
138
|
+
)) {
|
|
139
|
+
if (name.startsWith(componentName)) {
|
|
140
|
+
components.push(componentValue);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return components;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function executeExtensionsOnStartup() {
|
|
149
|
+
const extensionNames: string[] = Object.keys(ctx.extensions);
|
|
150
|
+
for (let index = 0; index < extensionNames.length; index++) {
|
|
151
|
+
const extensionName = extensionNames[index];
|
|
152
|
+
const extension = ctx.extensions[extensionName];
|
|
153
|
+
if (extension) {
|
|
154
|
+
if (extension.onStartup) {
|
|
155
|
+
await extension.onStartup(getExtensionUtils());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function getDashboardNavs(): DashboardNavs {
|
|
162
|
+
let navs: DashboardNavs = {
|
|
163
|
+
top: [],
|
|
164
|
+
middle: [],
|
|
165
|
+
bottom: [],
|
|
166
|
+
};
|
|
167
|
+
const extensionNames: string[] = Object.keys(ctx.extensions);
|
|
168
|
+
for (let index = 0; index < extensionNames.length; index++) {
|
|
169
|
+
const extensionName = extensionNames[index];
|
|
170
|
+
const extension = ctx.extensions[extensionName];
|
|
171
|
+
if (extension) {
|
|
172
|
+
if (extension.dashboardNavs && extension.dashboardNavs.top && navs.top) {
|
|
173
|
+
navs.top = [...navs.top, ...extension.dashboardNavs.top];
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
extension.dashboardNavs &&
|
|
177
|
+
extension.dashboardNavs.middle &&
|
|
178
|
+
navs.middle
|
|
179
|
+
) {
|
|
180
|
+
navs.middle = [...navs.middle, ...extension.dashboardNavs.middle];
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
extension.dashboardNavs &&
|
|
184
|
+
extension.dashboardNavs.bottom &&
|
|
185
|
+
navs.bottom
|
|
186
|
+
) {
|
|
187
|
+
navs.bottom = [...navs.bottom, ...extension.dashboardNavs.bottom];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return navs;
|
|
192
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as Studio } from "./components/Studio.svelte";
|
|
2
|
+
export { Lobb } from "./Lobb";
|
|
3
|
+
export * from "./utils";
|
|
4
|
+
export * from "./eventSystem";
|
|
5
|
+
export { ctx, lobb } from "./store.svelte";
|
|
6
|
+
export type * from "./store.types";
|
|
7
|
+
export declare function createSet(lastNumber: number): Set<number>;
|
|
8
|
+
export declare function moveElement<T>(array: T[], fromIndex: number, toIndex: number): T[];
|
|
9
|
+
export declare function pxToRem(px: number): number;
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Main Studio component
|
|
2
|
+
export { default as Studio } from "./components/Studio.svelte";
|
|
3
|
+
|
|
4
|
+
// Lobb client and utilities
|
|
5
|
+
export { Lobb } from "./Lobb";
|
|
6
|
+
export * from "./utils";
|
|
7
|
+
export * from "./eventSystem";
|
|
8
|
+
export { ctx, lobb } from "./store.svelte";
|
|
9
|
+
export type * from "./store.types";
|
|
10
|
+
|
|
11
|
+
export function createSet(lastNumber: number): Set<number> {
|
|
12
|
+
const set = new Set() as Set<number>;
|
|
13
|
+
for (let i = 0; i <= lastNumber; i++) {
|
|
14
|
+
set.add(i);
|
|
15
|
+
}
|
|
16
|
+
return set;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function moveElement<T>(array: T[], fromIndex: number, toIndex: number) {
|
|
20
|
+
if (
|
|
21
|
+
fromIndex < 0 || fromIndex >= array.length || toIndex < 0 ||
|
|
22
|
+
toIndex >= array.length
|
|
23
|
+
) {
|
|
24
|
+
throw new Error("Index out of bounds");
|
|
25
|
+
}
|
|
26
|
+
const [element] = array.splice(fromIndex, 1);
|
|
27
|
+
array.splice(toIndex, 0, element);
|
|
28
|
+
return array;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function pxToRem(px: number) {
|
|
32
|
+
const rootFontSize = parseFloat(
|
|
33
|
+
getComputedStyle(document.documentElement).fontSize,
|
|
34
|
+
);
|
|
35
|
+
return px / rootFontSize;
|
|
36
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CTX } from './store.types';
|
|
2
|
+
import { Lobb } from './Lobb';
|
|
3
|
+
import { toast } from 'svelte-sonner';
|
|
4
|
+
import pkg from '../../package.json';
|
|
5
|
+
|
|
6
|
+
if (!window.APP_ENV) {
|
|
7
|
+
window.APP_ENV = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (window.APP_ENV.LOBB_URL === '%LOBB_URL%') {
|
|
11
|
+
window.APP_ENV.LOBB_URL = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ctx: CTX = $state.raw({
|
|
15
|
+
studioVersion: pkg.version,
|
|
16
|
+
lobbUrl: window.APP_ENV.LOBB_URL || localStorage.getItem("lobb_url"),
|
|
17
|
+
extensions: {},
|
|
18
|
+
meta: {
|
|
19
|
+
collections: null,
|
|
20
|
+
extensions: null,
|
|
21
|
+
filter: null
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const lobb = new Lobb(ctx.lobbUrl);
|
|
26
|
+
|
|
27
|
+
// logging the message if got any bad responses
|
|
28
|
+
lobb.onResponse(async (response) => {
|
|
29
|
+
if (response.status >= 400) {
|
|
30
|
+
const body = await response.json();
|
|
31
|
+
toast.error(body.message);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Extension } from "../extensions/extension.types";
|
|
2
|
+
interface Collection {
|
|
3
|
+
category: string;
|
|
4
|
+
owner: string;
|
|
5
|
+
fields: Record<string, any>;
|
|
6
|
+
singleton: boolean;
|
|
7
|
+
}
|
|
8
|
+
type Collections = Record<string, Collection>;
|
|
9
|
+
interface Meta {
|
|
10
|
+
version: string;
|
|
11
|
+
relations: Array<any>;
|
|
12
|
+
collections: Collections;
|
|
13
|
+
extensions: Record<string, any>;
|
|
14
|
+
filter: any;
|
|
15
|
+
events: any[];
|
|
16
|
+
event_context_type: string;
|
|
17
|
+
studio_workflows: any[];
|
|
18
|
+
}
|
|
19
|
+
export interface CTX {
|
|
20
|
+
studioVersion: string;
|
|
21
|
+
lobbUrl: string | null;
|
|
22
|
+
extensions: Record<string, Extension>;
|
|
23
|
+
meta: Meta;
|
|
24
|
+
currentUrl: URL;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Extension } from "../extensions/extension.types";
|
|
2
|
+
|
|
3
|
+
interface Collection {
|
|
4
|
+
category: string;
|
|
5
|
+
owner: string;
|
|
6
|
+
fields: Record<string, any>;
|
|
7
|
+
singleton: boolean;
|
|
8
|
+
}
|
|
9
|
+
type Collections = Record<string, Collection>;
|
|
10
|
+
|
|
11
|
+
interface Meta {
|
|
12
|
+
version: string;
|
|
13
|
+
relations: Array<any>;
|
|
14
|
+
collections: Collections;
|
|
15
|
+
extensions: Record<string, any>;
|
|
16
|
+
filter: any;
|
|
17
|
+
events: any[];
|
|
18
|
+
event_context_type: string;
|
|
19
|
+
studio_workflows: any[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CTX {
|
|
23
|
+
studioVersion: string;
|
|
24
|
+
lobbUrl: string | null;
|
|
25
|
+
extensions: Record<string, Extension>;
|
|
26
|
+
meta: Meta;
|
|
27
|
+
currentUrl: URL;
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ClassValue } from "clsx";
|
|
2
|
+
import { MediaQuery } from 'svelte/reactivity';
|
|
3
|
+
export declare function cn(...inputs: ClassValue[]): string;
|
|
4
|
+
export type WithoutChild<T> = T extends {
|
|
5
|
+
child?: any;
|
|
6
|
+
} ? Omit<T, "child"> : T;
|
|
7
|
+
export type WithoutChildren<T> = T extends {
|
|
8
|
+
children?: any;
|
|
9
|
+
} ? Omit<T, "children"> : T;
|
|
10
|
+
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|
11
|
+
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
|
12
|
+
ref?: U | null;
|
|
13
|
+
};
|
|
14
|
+
export declare const mediaQueries: {
|
|
15
|
+
sm: MediaQuery;
|
|
16
|
+
md: MediaQuery;
|
|
17
|
+
lg: MediaQuery;
|
|
18
|
+
xl: MediaQuery;
|
|
19
|
+
'2xl': MediaQuery;
|
|
20
|
+
};
|
|
21
|
+
export declare function getFileFromUser(): Promise<import("browser-fs-access").FileWithHandle>;
|
|
22
|
+
export declare function getFieldRelation(collectionName: string, fieldName: string): any;
|
|
23
|
+
export declare function getDiscriminatorFieldRelation(collectionName: string, fieldName: string): any;
|
|
24
|
+
export declare function recordHasChildrean(collectionName: string): boolean;
|
|
25
|
+
export declare function calculateDrawerWidth(): number;
|
|
26
|
+
export declare function getChangedProperties(oldObj: Record<string, any>, newObj: Record<string, any>): Record<string, any>;
|
|
27
|
+
export declare function parseFunction(functionString: string): any;
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
import { fileOpen } from 'browser-fs-access';
|
|
5
|
+
import { MediaQuery } from 'svelte/reactivity';
|
|
6
|
+
import { ctx } from "./store.svelte";
|
|
7
|
+
|
|
8
|
+
export function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
|
|
16
|
+
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|
17
|
+
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export const mediaQueries = {
|
|
21
|
+
sm: new MediaQuery('min-width: 640px'),
|
|
22
|
+
md: new MediaQuery('min-width: 768px'),
|
|
23
|
+
lg: new MediaQuery('min-width: 1024px'),
|
|
24
|
+
xl: new MediaQuery('min-width: 1280px'),
|
|
25
|
+
'2xl': new MediaQuery('min-width: 1536px'),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function getFileFromUser() {
|
|
29
|
+
return await fileOpen()
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function getFieldRelation(collectionName: string, fieldName: string) {
|
|
33
|
+
const relations = ctx.meta.relations;
|
|
34
|
+
for (let index = 0; index < relations.length; index++) {
|
|
35
|
+
const relation = relations[index];
|
|
36
|
+
if (relation.from.collection === collectionName && relation.from.field === fieldName) {
|
|
37
|
+
return relation;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function getDiscriminatorFieldRelation(collectionName: string, fieldName: string) {
|
|
44
|
+
const relations = ctx.meta.relations;
|
|
45
|
+
for (let index = 0; index < relations.length; index++) {
|
|
46
|
+
const relation = relations[index];
|
|
47
|
+
if (relation.from.collection === collectionName && relation.from.discriminator === fieldName) {
|
|
48
|
+
return relation;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function recordHasChildrean(collectionName: string) {
|
|
55
|
+
for (let index = 0; index < ctx.meta.relations.length; index++) {
|
|
56
|
+
const relation = ctx.meta.relations[index];
|
|
57
|
+
if (relation.to.collection === collectionName) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function calculateDrawerWidth() {
|
|
65
|
+
const backgroundDrawerButtons = document.querySelectorAll(".backgroundDrawerButton");
|
|
66
|
+
const drawersCount = Array.from(backgroundDrawerButtons).length;
|
|
67
|
+
const width = 672 - (30 * drawersCount);
|
|
68
|
+
return width;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function getChangedProperties(oldObj: Record<string, any>, newObj: Record<string, any>) {
|
|
72
|
+
const changes: Record<string, any> = {};
|
|
73
|
+
for (const key of Object.keys(newObj)) {
|
|
74
|
+
if (oldObj[key] !== newObj[key]) {
|
|
75
|
+
changes[key] = newObj[key];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return changes;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function parseFunction(functionString: string) {
|
|
82
|
+
const functionObj = new Function("return " + functionString)();
|
|
83
|
+
return functionObj;
|
|
84
|
+
};
|