336 lines
13 KiB
JavaScript
336 lines
13 KiB
JavaScript
import { collectionsSidebar } from "./collectionsSidebar";
|
|
|
|
const SORT_QUERY_KEY = "sort";
|
|
const FILTER_QUERY_KEY = "filter";
|
|
const COLLECTION_QUERY_KEY = "collection";
|
|
const RECORD_QUERY_KEY = "record";
|
|
const LAST_ACTIVE_STORAGE_KEY = "pbLastActiveCollection";
|
|
const TOTAL_COUNT_REQUEST_KEY = "recordsTotalCountRequest";
|
|
|
|
export function pageCollections(route) {
|
|
app.store.activeCollection = route.query[COLLECTION_QUERY_KEY]?.[0]
|
|
|| window.localStorage.getItem(LAST_ACTIVE_STORAGE_KEY);
|
|
|
|
const pageData = store({
|
|
reset: null,
|
|
activeRecordIdOrModel: route.query[RECORD_QUERY_KEY]?.[0] || "",
|
|
sort: route.query[SORT_QUERY_KEY]?.[0] || "",
|
|
filter: route.query[FILTER_QUERY_KEY]?.[0] || "",
|
|
totalCount: 0,
|
|
isTotalCountLoading: false,
|
|
});
|
|
|
|
async function loadTotalCount() {
|
|
if (!app.store.activeCollection?.id) {
|
|
return;
|
|
}
|
|
|
|
pageData.isTotalCountLoading = true;
|
|
|
|
try {
|
|
const normalizedFilter = app.utils.normalizeSearchFilter(
|
|
pageData.filter,
|
|
app.store.activeCollection.fields.filter((f) => !f.hidden).map((f) => f.name),
|
|
);
|
|
|
|
const result = await app.pb.collection(app.store.activeCollection.name).getList(1, 1, {
|
|
requestKey: TOTAL_COUNT_REQUEST_KEY,
|
|
filter: normalizedFilter,
|
|
fields: "id",
|
|
});
|
|
|
|
pageData.totalCount = result.totalItems;
|
|
} catch (err) {
|
|
if (!err.isAbort) {
|
|
pageData.totalCount = 0;
|
|
console.warn("failed to load total count:", err);
|
|
}
|
|
}
|
|
|
|
pageData.isTotalCountLoading = false;
|
|
}
|
|
|
|
function refreshRecordsList() {
|
|
pageData.reset = Date.now();
|
|
}
|
|
|
|
const watchers = [
|
|
watch(
|
|
() => (app.store.activeCollection?.name || "") + (app.store.activeCollection?.updated || ""),
|
|
(newVal, oldVal) => {
|
|
app.store.title = app.store.activeCollection?.name || "Collections";
|
|
|
|
// skip unnecessery initial params replacement
|
|
if (!oldVal) {
|
|
return;
|
|
}
|
|
|
|
// reset filter and sort params on collection change
|
|
if (oldVal != newVal) {
|
|
pageData.filter = "";
|
|
pageData.sort = "";
|
|
}
|
|
|
|
app.utils.replaceHashQueryParams({
|
|
[COLLECTION_QUERY_KEY]: app.store.activeCollection?.name,
|
|
[FILTER_QUERY_KEY]: pageData.filter || null,
|
|
[SORT_QUERY_KEY]: pageData.sort || null,
|
|
}, newVal != oldVal ? true : null);
|
|
|
|
if (app.store.activeCollection?.id) {
|
|
window.localStorage.setItem(LAST_ACTIVE_STORAGE_KEY, app.store.activeCollection.id);
|
|
} else {
|
|
window.localStorage.removeItem(LAST_ACTIVE_STORAGE_KEY);
|
|
}
|
|
},
|
|
),
|
|
watch(
|
|
() => [pageData.filter, pageData.sort],
|
|
(newVal, oldVal) => {
|
|
if (!oldVal) {
|
|
return;
|
|
}
|
|
|
|
app.utils.replaceHashQueryParams({
|
|
[FILTER_QUERY_KEY]: pageData.filter || null,
|
|
[SORT_QUERY_KEY]: pageData.sort || null,
|
|
});
|
|
},
|
|
),
|
|
watch(
|
|
() => (pageData.activeRecordIdOrModel || "") + (app.store.activeCollection?.id || ""),
|
|
(newVal, oldVal) => {
|
|
if (!pageData.activeRecordIdOrModel) {
|
|
app.utils.replaceHashQueryParams({
|
|
[RECORD_QUERY_KEY]: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
// no change or the collection model is still loading
|
|
if (newVal == oldVal || !app.store.activeCollection?.id) {
|
|
return;
|
|
}
|
|
|
|
const recordData = typeof pageData.activeRecordIdOrModel == "string"
|
|
? {
|
|
id: pageData.activeRecordIdOrModel,
|
|
collectionId: app.store.activeCollection?.id,
|
|
collectionName: app.store.activeCollection?.name,
|
|
}
|
|
: pageData.activeRecordIdOrModel;
|
|
|
|
app.utils.replaceHashQueryParams({
|
|
[RECORD_QUERY_KEY]: recordData.id || null,
|
|
});
|
|
|
|
// force close any previous modal
|
|
app.modals.close(null, true);
|
|
|
|
if (app.store.activeCollection?.type == "view") {
|
|
app.modals.openRecordPreview(recordData, {
|
|
onafterclose: () => {
|
|
pageData.activeRecordIdOrModel = "";
|
|
},
|
|
});
|
|
} else {
|
|
app.modals.openRecordUpsert(app.store.activeCollection, recordData, {
|
|
onafterclose: () => {
|
|
pageData.activeRecordIdOrModel = "";
|
|
},
|
|
});
|
|
}
|
|
},
|
|
),
|
|
watch(
|
|
() => [app.store.activeCollection?.id, pageData.filter, pageData.reset],
|
|
() => loadTotalCount(),
|
|
),
|
|
];
|
|
|
|
const documentEvents = {
|
|
"record:save": (e) => {
|
|
if (e.detail.collectionId != app.store.activeCollection?.id) {
|
|
return;
|
|
}
|
|
|
|
pageData.totalCount++;
|
|
},
|
|
"record:delete": (e) => {
|
|
if (
|
|
// check both because for delete we don't know which one was assigned to
|
|
e.detail.collectionId != app.store.activeCollection?.id
|
|
&& e.detail.collectionName != app.store.activeCollection?.name
|
|
) {
|
|
return;
|
|
}
|
|
|
|
pageData.totalCount--;
|
|
},
|
|
};
|
|
|
|
return t.div(
|
|
{
|
|
pbEvent: "pageCollections",
|
|
className: "page",
|
|
onmount: () => {
|
|
// refresh if necesser the cached collections in the background
|
|
if (!app.store.isLoadingCollections) {
|
|
app.store.silentlyReloadCollections();
|
|
}
|
|
|
|
for (let event in documentEvents) {
|
|
document.addEventListener(event, documentEvents[event]);
|
|
}
|
|
},
|
|
onunmount: () => {
|
|
app.pb.cancelRequest(TOTAL_COUNT_REQUEST_KEY);
|
|
|
|
watchers.forEach((w) => w?.unwatch());
|
|
|
|
for (let event in documentEvents) {
|
|
document.removeEventListener(event, documentEvents[event]);
|
|
}
|
|
},
|
|
},
|
|
() => collectionsSidebar(),
|
|
t.div(
|
|
{ className: "page-content full-height" },
|
|
t.header(
|
|
{ className: "page-header compact flex-nowrap" },
|
|
t.nav(
|
|
{ className: "breadcrumbs" },
|
|
t.div(null, "Collections"),
|
|
() => {
|
|
if (app.store.activeCollection?.name) {
|
|
return t.div({
|
|
title: app.store.activeCollection.name,
|
|
textContent: app.store.activeCollection.name,
|
|
});
|
|
}
|
|
},
|
|
),
|
|
t.div(
|
|
{
|
|
hidden: () => !app.store.activeCollection?.id,
|
|
pbEvent: "pageHeaderSecondaryBtns",
|
|
className: "page-header-secondary-btns",
|
|
},
|
|
t.button(
|
|
{
|
|
type: "button",
|
|
className: "btn circle transparent secondary tooltip-bottom btn-collection-settings",
|
|
ariaLabel: app.attrs.tooltip("Collection settings"),
|
|
onclick: () => {
|
|
app.modals.openCollectionUpsert(app.store.activeCollection, {
|
|
ontruncate: () => refreshRecordsList(),
|
|
onsave: (collection, isNew) => {
|
|
if (isNew) {
|
|
// e.g. in case of a duplicate or modal state reset
|
|
app.store.activeCollection = collection.id;
|
|
} else {
|
|
refreshRecordsList();
|
|
}
|
|
},
|
|
});
|
|
},
|
|
},
|
|
t.i({ className: "ri-settings-3-line", ariaHidden: true }),
|
|
),
|
|
app.components.refreshButton({
|
|
onclick: () => refreshRecordsList(),
|
|
}),
|
|
),
|
|
t.div(
|
|
{
|
|
hidden: () => !app.store.activeCollection?.id,
|
|
pbEvent: "pageHeaderPrimaryBtns",
|
|
className: "page-header-primary-btns",
|
|
},
|
|
t.button(
|
|
{
|
|
type: "button",
|
|
className: "btn outline api-preview-btn",
|
|
onclick: () => app.modals.openApiPreview(app.store.activeCollection),
|
|
},
|
|
t.i({ className: "ri-code-s-slash-line", ariaHidden: true }),
|
|
t.span({ className: "txt", textContent: "API preview" }),
|
|
),
|
|
() => {
|
|
if (app.store.activeCollection?.type == "view") {
|
|
return;
|
|
}
|
|
|
|
return t.button(
|
|
{
|
|
type: "button",
|
|
className: "btn new-record-btn",
|
|
onclick: () => app.modals.openRecordUpsert(app.store.activeCollection),
|
|
},
|
|
t.i({ className: "ri-add-line", ariaHidden: true }),
|
|
t.span({ className: "txt", textContent: "New Record" }),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
// page loader
|
|
t.div(
|
|
{
|
|
hidden: () => !app.store.isLoadingCollections || app.store.activeCollection?.id,
|
|
className: "block txt-center p-base",
|
|
},
|
|
t.span({ className: "loader lg" }),
|
|
),
|
|
// no selected collection
|
|
t.div(
|
|
{
|
|
hidden: () => app.store.isLoadingCollections || app.store.activeCollection?.id,
|
|
className: "block txt-center p-base",
|
|
},
|
|
t.h6(
|
|
{ className: "txt" },
|
|
() => {
|
|
if (app.store.collections?.length) {
|
|
return "Select collection from the sidebar.";
|
|
}
|
|
return "No collections found.";
|
|
},
|
|
),
|
|
),
|
|
// records list
|
|
app.components.recordsSearchbar({
|
|
hidden: () => !app.store.activeCollection?.id,
|
|
collection: () => app.store.activeCollection,
|
|
value: () => pageData.filter,
|
|
onsubmit: (newFilter) => (pageData.filter = newFilter),
|
|
}),
|
|
app.components.recordsList({
|
|
className: "m-t-sm",
|
|
reset: () => pageData.reset,
|
|
hidden: () => !app.store.activeCollection?.id,
|
|
collection: () => app.store.activeCollection,
|
|
filter: () => pageData.filter,
|
|
sort: () => pageData.sort,
|
|
onselect: (record) => {
|
|
pageData.activeRecordIdOrModel = record;
|
|
},
|
|
onchange: (newFilter, newSort) => {
|
|
pageData.filter = newFilter;
|
|
pageData.sort = newSort;
|
|
},
|
|
}),
|
|
t.footer(
|
|
{ className: "page-footer" },
|
|
t.span(
|
|
{
|
|
className: () => `total-count ${pageData.isTotalCountLoading ? "faded" : ""}`,
|
|
},
|
|
"Total: ",
|
|
() => pageData.totalCount,
|
|
),
|
|
app.components.credits(),
|
|
),
|
|
),
|
|
);
|
|
}
|