@protobase/ui 0.12.3 → 0.12.5
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/dist/app/app.js +5 -0
- package/dist/app/error-reporting.js +25 -0
- package/dist/app/pages/custom-boundary.js +4 -0
- package/dist/app/record-page.js +115 -111
- package/dist/assistant-dock/widget-view.js +4 -0
- package/dist/data/meta-data.d.ts +5 -0
- package/dist/data/use-meta.js +1 -0
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-CNHqcIWP.js +65 -0
- package/dist/shell/assets/{index-ntYv19kQ.js → index-B2A34Ndi.js} +1 -1
- package/dist/shell/assets/{index-DheSSTn2.css → index-BVOqIEYd.css} +1 -1
- package/dist/shell/assets/prod-YZ9ZMG1O.js +15 -0
- package/dist/shell/assets/{shared-protobase-ui-eH-UjL47.js → shared-protobase-ui-BZ7z7Y8G.js} +1 -1
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +5 -4
- package/dist/shell/assets/app-Cqr5Ze7R.js +0 -65
package/dist/app/app.js
CHANGED
|
@@ -11,6 +11,7 @@ import { useRecord } from "../data/use-record.js";
|
|
|
11
11
|
import { recordTitle } from "../live/model-helpers.js";
|
|
12
12
|
import { humanize } from "../live/naming.js";
|
|
13
13
|
import { ToastProvider } from "../toasts/toast-provider.js";
|
|
14
|
+
import { startErrorReporting } from "./error-reporting.js";
|
|
14
15
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
15
16
|
import { AssistantButton, AssistantPanel } from "./assistant-shell.js";
|
|
16
17
|
import { bottomBarFromMeta } from "./bottom-bar-from-meta.js";
|
|
@@ -101,6 +102,10 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
101
102
|
page: params.size > 0 ? `${path}?${params}` : path,
|
|
102
103
|
onClose: () => setAssistantOpen(false)
|
|
103
104
|
});
|
|
105
|
+
const errorReporting = meta.errorReporting;
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (errorReporting) startErrorReporting(errorReporting);
|
|
108
|
+
}, [errorReporting]);
|
|
104
109
|
const runtimeUrl = meta.runtime?.url;
|
|
105
110
|
const runtimeButton = runtimeUrl && /* @__PURE__ */ jsx(RuntimeUpdateButton, {
|
|
106
111
|
url: runtimeUrl,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { redactSecrets } from "@protobase/schema";
|
|
2
|
+
//#region src/app/error-reporting.ts
|
|
3
|
+
let loading;
|
|
4
|
+
let capture;
|
|
5
|
+
/**
|
|
6
|
+
* Sends the app's uncaught errors to the error tracker `/meta` names, from the first call on; the Sentry SDK loads only
|
|
7
|
+
* then, so an app without a DSN never loads it. Errors only, with no performance tracing and no session tracking,
|
|
8
|
+
* every event without its secrets (see `redactSecrets`).
|
|
9
|
+
*/
|
|
10
|
+
const startErrorReporting = ({ dsn, environment }) => {
|
|
11
|
+
loading ??= import("@sentry/browser").then((sentry) => {
|
|
12
|
+
sentry.init({
|
|
13
|
+
dsn,
|
|
14
|
+
...environment && { environment },
|
|
15
|
+
integrations: (defaults) => defaults.filter((integration) => integration.name !== "BrowserSession"),
|
|
16
|
+
beforeSend: (event) => redactSecrets(event)
|
|
17
|
+
});
|
|
18
|
+
capture = (error, componentStack) => sentry.captureException(error, componentStack ? { contexts: { react: { componentStack } } } : void 0);
|
|
19
|
+
});
|
|
20
|
+
return loading;
|
|
21
|
+
};
|
|
22
|
+
/** Reports an error a boundary caught while drawing, with the components it was drawn in; nothing before error reporting started. */
|
|
23
|
+
const reportRenderError = (error, componentStack) => capture?.(error, componentStack ?? void 0);
|
|
24
|
+
//#endregion
|
|
25
|
+
export { reportRenderError, startErrorReporting };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportRenderError } from "../error-reporting.js";
|
|
1
2
|
import { Problem } from "./problem.js";
|
|
2
3
|
import { Component } from "react";
|
|
3
4
|
import { jsxs } from "react/jsx-runtime";
|
|
@@ -8,6 +9,9 @@ var CustomBoundary = class extends Component {
|
|
|
8
9
|
static getDerivedStateFromError() {
|
|
9
10
|
return { failed: true };
|
|
10
11
|
}
|
|
12
|
+
componentDidCatch(error, info) {
|
|
13
|
+
reportRenderError(error, info.componentStack);
|
|
14
|
+
}
|
|
11
15
|
render() {
|
|
12
16
|
if (!this.state.failed) return this.props.children;
|
|
13
17
|
return this.props.fallback === void 0 ? /* @__PURE__ */ jsxs(Problem, { children: [this.props.name, " could not be drawn."] }) : this.props.fallback;
|
package/dist/app/record-page.js
CHANGED
|
@@ -114,6 +114,7 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
|
|
|
114
114
|
const permissions = usePermissions(model.name);
|
|
115
115
|
const restore = useRestore(model);
|
|
116
116
|
const canUpdate = can(permissions, "update") && stored.permissions?.update !== false;
|
|
117
|
+
const canDelete = can(permissions, "delete") && stored.permissions?.delete !== false;
|
|
117
118
|
const remove = useDeleteFlow({
|
|
118
119
|
model,
|
|
119
120
|
view,
|
|
@@ -210,7 +211,7 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
|
|
|
210
211
|
onClick: editor.discard,
|
|
211
212
|
children: "Discard"
|
|
212
213
|
}),
|
|
213
|
-
|
|
214
|
+
canDelete && /* @__PURE__ */ jsx(Button, {
|
|
214
215
|
variant: "danger",
|
|
215
216
|
className: "max-md:min-h-11",
|
|
216
217
|
onClick: () => void remove.start({
|
|
@@ -231,122 +232,125 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
|
|
|
231
232
|
})
|
|
232
233
|
] });
|
|
233
234
|
return /* @__PURE__ */ jsxs("div", {
|
|
234
|
-
className: "min-h-0 flex-1
|
|
235
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
235
236
|
children: [
|
|
236
237
|
remove.dialogs,
|
|
237
|
-
/* @__PURE__ */
|
|
238
|
-
className: "
|
|
239
|
-
children:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
children: [/* @__PURE__ */ jsx(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
238
|
+
/* @__PURE__ */ jsx("div", {
|
|
239
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
240
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
241
|
+
className: "mx-auto max-w-[1100px] px-4 pb-8 pt-6 md:px-10 md:py-8",
|
|
242
|
+
children: [
|
|
243
|
+
/* @__PURE__ */ jsx(RecordHeader, {
|
|
244
|
+
collection: view?.names?.plural ?? humanize(model.name),
|
|
245
|
+
title,
|
|
246
|
+
status: {
|
|
247
|
+
label: status ? enumLabel(status, hint("status")?.valueLabels) : singular,
|
|
248
|
+
tone: status ? toneFor(status) : "neutral"
|
|
249
|
+
},
|
|
250
|
+
lastEditedBy: { ago: updated === void 0 ? "recently" : formatRelativeTime(updated) },
|
|
251
|
+
actions: /* @__PURE__ */ jsx("div", {
|
|
252
|
+
className: "hidden items-center gap-4 md:flex",
|
|
253
|
+
children: saveControls
|
|
254
|
+
})
|
|
255
|
+
}),
|
|
256
|
+
editor.message && /* @__PURE__ */ jsx("div", {
|
|
257
|
+
className: "mt-4",
|
|
258
|
+
children: /* @__PURE__ */ jsx(ErrorBanner, {
|
|
259
|
+
title: "Not saved",
|
|
260
|
+
error: new Error(editor.message)
|
|
261
|
+
})
|
|
262
|
+
}),
|
|
263
|
+
editor.linesConflict && /* @__PURE__ */ jsx("div", {
|
|
264
|
+
className: "mt-4",
|
|
265
|
+
children: /* @__PURE__ */ jsx(ErrorBanner, {
|
|
266
|
+
title: "The lines were changed by someone else",
|
|
267
|
+
error: /* @__PURE__ */ new Error("The lines were reloaded. Your new order is kept: Save again to apply it, or Discard to drop it.")
|
|
268
|
+
})
|
|
269
|
+
}),
|
|
270
|
+
editor.restored && /* @__PURE__ */ jsxs("div", {
|
|
271
|
+
className: "mt-4 flex items-center justify-between gap-3 rounded-lg border bg-surface px-3 py-2 text-[13px]",
|
|
272
|
+
role: "status",
|
|
273
|
+
children: [/* @__PURE__ */ jsx("span", { children: "Your unsaved changes were restored." }), /* @__PURE__ */ jsxs("span", {
|
|
274
|
+
className: "flex gap-2",
|
|
275
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
276
|
+
size: "sm",
|
|
277
|
+
onClick: editor.dismissRestored,
|
|
278
|
+
children: "Keep"
|
|
279
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
280
|
+
size: "sm",
|
|
281
|
+
onClick: editor.discard,
|
|
282
|
+
children: "Discard"
|
|
283
|
+
})]
|
|
280
284
|
})]
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
285
|
+
}),
|
|
286
|
+
/* @__PURE__ */ jsxs("div", {
|
|
287
|
+
className: "grid grid-cols-1 gap-8 md:grid-cols-[minmax(0,1fr)_300px] md:gap-12",
|
|
288
|
+
children: [/* @__PURE__ */ jsxs("main", {
|
|
289
|
+
className: "divide-y divide-border",
|
|
290
|
+
children: [layout.flatMap((item, index) => {
|
|
291
|
+
if (item.kind === "related") return [/* @__PURE__ */ jsx(RelatedSection, {
|
|
292
|
+
item,
|
|
293
|
+
recordKey
|
|
294
|
+
}, `related:${item.title}`)];
|
|
295
|
+
if (item.kind === "custom") return item.sidebar ? [] : [/* @__PURE__ */ jsx(CustomSection, {
|
|
296
|
+
item,
|
|
297
|
+
scope
|
|
298
|
+
}, `custom:${index}`)];
|
|
299
|
+
if (item.kind !== "section") return [];
|
|
300
|
+
const fields = item.fields.filter((name) => model.fields[name] && visible(name));
|
|
301
|
+
if (fields.length === 0) return [];
|
|
302
|
+
return [/* @__PURE__ */ jsx(Section, {
|
|
303
|
+
title: item.title,
|
|
304
|
+
help: item.help,
|
|
305
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
306
|
+
className: "grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2",
|
|
307
|
+
children: fields.map(fieldView)
|
|
308
|
+
})
|
|
309
|
+
}, item.title)];
|
|
310
|
+
}), linesModel && /* @__PURE__ */ jsx(RecordLines, {
|
|
311
|
+
lines: linesModel,
|
|
312
|
+
draft: linesDraft,
|
|
313
|
+
vatRate: Number(stored.record.vatRate ?? 21) / 100,
|
|
314
|
+
saving: editor.saving
|
|
315
|
+
})]
|
|
316
|
+
}), /* @__PURE__ */ jsxs("aside", {
|
|
317
|
+
className: "space-y-4 md:pt-8",
|
|
318
|
+
children: [
|
|
319
|
+
/* @__PURE__ */ jsx(RecordActions, {
|
|
320
|
+
model,
|
|
321
|
+
stored,
|
|
322
|
+
recordKey
|
|
323
|
+
}),
|
|
324
|
+
firstSidebar === -1 && summary,
|
|
325
|
+
layout.map((item, index) => item.kind === "custom" && item.sidebar ? /* @__PURE__ */ jsx(CustomSection, {
|
|
326
|
+
item,
|
|
327
|
+
scope
|
|
328
|
+
}, `custom:${index}`) : index === firstSidebar ? /* @__PURE__ */ jsx(Fragment, { children: summary }, "summary") : null),
|
|
329
|
+
/* @__PURE__ */ jsx(MetadataCard, {
|
|
330
|
+
title: "Details",
|
|
331
|
+
rows: [auditField(model, "created"), updatedAt].flatMap((name) => {
|
|
332
|
+
const field = name ? model.fields[name] : void 0;
|
|
333
|
+
return name && field && visible(name) ? [{
|
|
334
|
+
label: hint(name)?.label ?? humanize(name),
|
|
335
|
+
value: displayValue(field, stored.record[name], hint(name))
|
|
336
|
+
}] : [];
|
|
337
|
+
})
|
|
338
|
+
}),
|
|
339
|
+
related.records.length > 0 && /* @__PURE__ */ jsx(SidebarCard, {
|
|
340
|
+
title: "Related records",
|
|
341
|
+
children: /* @__PURE__ */ jsx(RelatedRecords, {
|
|
342
|
+
records: related.records,
|
|
343
|
+
onNavigate: open
|
|
344
|
+
})
|
|
305
345
|
})
|
|
306
|
-
|
|
307
|
-
}), linesModel && /* @__PURE__ */ jsx(RecordLines, {
|
|
308
|
-
lines: linesModel,
|
|
309
|
-
draft: linesDraft,
|
|
310
|
-
vatRate: Number(stored.record.vatRate ?? 21) / 100,
|
|
311
|
-
saving: editor.saving
|
|
346
|
+
]
|
|
312
347
|
})]
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
/* @__PURE__ */ jsx(RecordActions, {
|
|
317
|
-
model,
|
|
318
|
-
stored,
|
|
319
|
-
recordKey
|
|
320
|
-
}),
|
|
321
|
-
firstSidebar === -1 && summary,
|
|
322
|
-
layout.map((item, index) => item.kind === "custom" && item.sidebar ? /* @__PURE__ */ jsx(CustomSection, {
|
|
323
|
-
item,
|
|
324
|
-
scope
|
|
325
|
-
}, `custom:${index}`) : index === firstSidebar ? /* @__PURE__ */ jsx(Fragment, { children: summary }, "summary") : null),
|
|
326
|
-
/* @__PURE__ */ jsx(MetadataCard, {
|
|
327
|
-
title: "Details",
|
|
328
|
-
rows: [auditField(model, "created"), updatedAt].flatMap((name) => {
|
|
329
|
-
const field = name ? model.fields[name] : void 0;
|
|
330
|
-
return name && field && visible(name) ? [{
|
|
331
|
-
label: hint(name)?.label ?? humanize(name),
|
|
332
|
-
value: displayValue(field, stored.record[name], hint(name))
|
|
333
|
-
}] : [];
|
|
334
|
-
})
|
|
335
|
-
}),
|
|
336
|
-
related.records.length > 0 && /* @__PURE__ */ jsx(SidebarCard, {
|
|
337
|
-
title: "Related records",
|
|
338
|
-
children: /* @__PURE__ */ jsx(RelatedRecords, {
|
|
339
|
-
records: related.records,
|
|
340
|
-
onNavigate: open
|
|
341
|
-
})
|
|
342
|
-
})
|
|
343
|
-
]
|
|
344
|
-
})]
|
|
345
|
-
})
|
|
346
|
-
]
|
|
348
|
+
})
|
|
349
|
+
]
|
|
350
|
+
})
|
|
347
351
|
}),
|
|
348
|
-
/* @__PURE__ */ jsx("div", {
|
|
349
|
-
className: "
|
|
352
|
+
(editor.dirty || canDelete || canUpdate) && /* @__PURE__ */ jsx("div", {
|
|
353
|
+
className: "flex shrink-0 items-center gap-2 border-t bg-background p-3 md:hidden",
|
|
350
354
|
children: saveControls
|
|
351
355
|
})
|
|
352
356
|
]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ActionCard } from "../action-card/action-card.js";
|
|
2
|
+
import { reportRenderError } from "../app/error-reporting.js";
|
|
2
3
|
import { useProjectUi } from "../app/pages/project-ui.js";
|
|
3
4
|
import { Component } from "react";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -20,6 +21,9 @@ var WidgetBoundary = class extends Component {
|
|
|
20
21
|
static getDerivedStateFromError() {
|
|
21
22
|
return { failed: true };
|
|
22
23
|
}
|
|
24
|
+
componentDidCatch(error, info) {
|
|
25
|
+
reportRenderError(error, info.componentStack);
|
|
26
|
+
}
|
|
23
27
|
render() {
|
|
24
28
|
return this.state.failed ? /* @__PURE__ */ jsx(Fallback, {
|
|
25
29
|
part: this.props.part,
|
package/dist/data/meta-data.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ export type MetaData = {
|
|
|
20
20
|
runtime?: {
|
|
21
21
|
url: string;
|
|
22
22
|
};
|
|
23
|
+
/** The error tracker the app sends its errors to, when the app reports them (see `startErrorReporting`). */
|
|
24
|
+
errorReporting?: {
|
|
25
|
+
dsn: string;
|
|
26
|
+
environment?: string;
|
|
27
|
+
};
|
|
23
28
|
/** The plugins the app loads for the user (see `PluginGate`). */
|
|
24
29
|
plugins?: PluginRef[];
|
|
25
30
|
/** The other APIs the app draws resources from for the user, and how far their `/meta` has loaded (see `withSources`). */
|
package/dist/data/use-meta.js
CHANGED
|
@@ -34,6 +34,7 @@ const useMeta = () => {
|
|
|
34
34
|
...result.meta.bottomBar && { bottomBar: result.meta.bottomBar },
|
|
35
35
|
...result.meta.assistant && { assistant: result.meta.assistant },
|
|
36
36
|
...result.meta.runtime && { runtime: result.meta.runtime },
|
|
37
|
+
...result.meta.errorReporting && { errorReporting: result.meta.errorReporting },
|
|
37
38
|
...result.meta.plugins && { plugins: result.meta.plugins },
|
|
38
39
|
...result.meta.sources && { sources: result.meta.sources.map((source) => ({
|
|
39
40
|
source,
|
package/dist/package.js
CHANGED