preload the record preview to minimize content jumps
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
- Added backups list scroll container ([#7655](https://github.com/pocketbase/pocketbase/issues/7655)).
|
- Added backups list scroll container ([#7655](https://github.com/pocketbase/pocketbase/issues/7655)).
|
||||||
|
|
||||||
- Optimized record upsert panel loading to minimize layout jumps.
|
- Optimized record upsert and preview modals loading to minimize layout jumps.
|
||||||
|
|
||||||
|
|
||||||
## v0.37.3
|
## v0.37.3
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
2
ui/dist/index.html
vendored
2
ui/dist/index.html
vendored
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<!-- prism -->
|
<!-- prism -->
|
||||||
<script src="./libs/prism/prism.js" data-manual></script>
|
<script src="./libs/prism/prism.js" data-manual></script>
|
||||||
<script type="module" crossorigin src="./assets/index-CzeH6zOJ.js"></script>
|
<script type="module" crossorigin src="./assets/index-C2jyM0t-.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="./assets/pocketbase.es-B_4DUNUU.js">
|
<link rel="modulepreload" crossorigin href="./assets/pocketbase.es-B_4DUNUU.js">
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-h3OAAQQg.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-h3OAAQQg.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -54,6 +54,18 @@ function copyJSON(record) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recordPreviewModal(rawRecord, modalSettings) {
|
function recordPreviewModal(rawRecord, modalSettings) {
|
||||||
|
if (!rawRecord?.id) {
|
||||||
|
app.toasts.error("Failed to load record.");
|
||||||
|
console.warn("[recordPreviewModal] missing required record id field:", rawRecord);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rawRecord.collectionId && !rawRecord.collectionName) {
|
||||||
|
app.toasts.error("Failed to load record.");
|
||||||
|
console.warn("[recordPreviewModal] missing required collectionId or collectionName field:", rawRecord);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let modal;
|
let modal;
|
||||||
|
|
||||||
const uniqueId = app.utils.randomString();
|
const uniqueId = app.utils.randomString();
|
||||||
@@ -69,39 +81,31 @@ function recordPreviewModal(rawRecord, modalSettings) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function loadRecord() {
|
async function loadRecord() {
|
||||||
if (!rawRecord?.id) {
|
|
||||||
app.toasts.error("Failed to load record.");
|
|
||||||
setTimeout(() => app.modals.close(modal), 0);
|
|
||||||
console.warn("[recordPreviewModal] missing required record id field:", rawRecord);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rawRecord.collectionId && !rawRecord.collectionName) {
|
|
||||||
app.toasts.error("Failed to load record.");
|
|
||||||
setTimeout(() => app.modals.close(modal), 0);
|
|
||||||
console.warn("[recordPreviewModal] missing required collectionId or collectionName field:", rawRecord);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.isLoading = true;
|
data.isLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eagerly expand first level presentable relations (if any and the collections are loaded)
|
// preload to minimize content jumps
|
||||||
|
data.record = JSON.parse(JSON.stringify(rawRecord));
|
||||||
|
|
||||||
|
// eagerly expand first level relations (if any and the collections are loaded)
|
||||||
let relExpands = [];
|
let relExpands = [];
|
||||||
const presentableRelationFields = data.collection?.fields?.filter(
|
const presentableRelationFields = data.collection?.fields?.filter(
|
||||||
(f) => !f.hidden && f.presentable && f.type == "relation",
|
(f) => !f.hidden && f.type == "relation",
|
||||||
) || [];
|
) || [];
|
||||||
for (let field of presentableRelationFields) {
|
for (let field of presentableRelationFields) {
|
||||||
relExpands.push(field.name);
|
relExpands.push(field.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.record = await app.pb
|
const record = await app.pb
|
||||||
.collection(rawRecord.collectionId || rawRecord.collectionName)
|
.collection(rawRecord.collectionName || rawRecord.collectionId)
|
||||||
.getOne(rawRecord.id, {
|
.getOne(rawRecord.id, {
|
||||||
requestKey: "record_preview_" + rawRecord.id,
|
requestKey: "record_preview_" + rawRecord.id,
|
||||||
expand: relExpands.join(",") || undefined,
|
expand: relExpands.join(",") || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// populate with an up-to-date fields
|
||||||
|
Object.assign(data.record, record);
|
||||||
|
|
||||||
data.isLoading = false;
|
data.isLoading = false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!err?.isAbort) {
|
if (!err?.isAbort) {
|
||||||
@@ -130,10 +134,6 @@ function recordPreviewModal(rawRecord, modalSettings) {
|
|||||||
modalSettings.onafterclose?.(el);
|
modalSettings.onafterclose?.(el);
|
||||||
el?.remove();
|
el?.remove();
|
||||||
},
|
},
|
||||||
onmount: (el) => {
|
|
||||||
},
|
|
||||||
onunmount: (el) => {
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
t.header(
|
t.header(
|
||||||
{ className: "modal-header" },
|
{ className: "modal-header" },
|
||||||
@@ -145,12 +145,15 @@ function recordPreviewModal(rawRecord, modalSettings) {
|
|||||||
t.button(
|
t.button(
|
||||||
{
|
{
|
||||||
title: "More options",
|
title: "More options",
|
||||||
className: "btn sm circle transparent m-l-auto",
|
className: () => `btn sm circle transparent m-l-auto ${data.isLoading ? "loading" : ""}`,
|
||||||
|
disabled: () => data.isLoading,
|
||||||
"html-popovertarget": uniqueId + "preview-dropdown",
|
"html-popovertarget": uniqueId + "preview-dropdown",
|
||||||
},
|
},
|
||||||
t.i({ className: "ri-more-line", ariaHidden: true }),
|
t.i({ className: "ri-more-line", ariaHidden: true }),
|
||||||
),
|
),
|
||||||
t.div({ id: uniqueId + "preview-dropdown", className: "dropdown", popover: "auto" }, (el) => {
|
t.div(
|
||||||
|
{ id: uniqueId + "preview-dropdown", className: "dropdown", popover: "auto" },
|
||||||
|
(el) => {
|
||||||
return t.button(
|
return t.button(
|
||||||
{
|
{
|
||||||
className: "dropdown-item",
|
className: "dropdown-item",
|
||||||
@@ -162,28 +165,12 @@ function recordPreviewModal(rawRecord, modalSettings) {
|
|||||||
t.i({ className: "ri-braces-line", ariaHidden: true }),
|
t.i({ className: "ri-braces-line", ariaHidden: true }),
|
||||||
t.span({ className: "txt" }, "Copy JSON"),
|
t.span({ className: "txt" }, "Copy JSON"),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
),
|
),
|
||||||
t.div({ className: "modal-content" }, () => {
|
),
|
||||||
// loader
|
t.div(
|
||||||
if (data.isLoading || !data.record?.id || !data.collection?.id) {
|
{ className: "modal-content" },
|
||||||
return t.table(
|
t.table(
|
||||||
null,
|
|
||||||
t.tbody(null, () => {
|
|
||||||
const totalRows = data.collection?.fields?.filter((f) => f.type != "password").length || 1;
|
|
||||||
const rows = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < totalRows; i++) {
|
|
||||||
rows.push(t.tr(null, t.td(null, t.span({ className: "skeleton-loader" }))));
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// attrs
|
|
||||||
return t.table(
|
|
||||||
{
|
{
|
||||||
pbEvent: "recordPreviewTable",
|
pbEvent: "recordPreviewTable",
|
||||||
className: "record-preview-table responsive-table",
|
className: "record-preview-table responsive-table",
|
||||||
@@ -219,8 +206,8 @@ function recordPreviewModal(rawRecord, modalSettings) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
t.footer(
|
t.footer(
|
||||||
{ className: "modal-footer" },
|
{ className: "modal-footer" },
|
||||||
t.button(
|
t.button(
|
||||||
@@ -234,7 +221,8 @@ function recordPreviewModal(rawRecord, modalSettings) {
|
|||||||
t.button(
|
t.button(
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
className: "btn",
|
className: () => `btn ${data.isLoading ? "loading" : ""}`,
|
||||||
|
disabled: () => data.isLoading,
|
||||||
onclick: () => downloadJSON(data.record),
|
onclick: () => downloadJSON(data.record),
|
||||||
},
|
},
|
||||||
t.i({ className: "ri-download-line", ariaHidden: true }),
|
t.i({ className: "ri-download-line", ariaHidden: true }),
|
||||||
|
|||||||
Reference in New Issue
Block a user