window.app = window.app || {}; window.app.modals = window.app.modals || {}; window.app.modals.openCollectionsOverview = function(settings = { onbeforeopen: null, onafteropen: null, onbeforeclose: null, onafterclose: null, }) { const modal = collectionsOverviewModal(settings); if (!modal) { return; } document.body.appendChild(modal); app.modals.open(modal); }; function collectionsOverviewModal(settings = {}) { const uniqueId = "overview_modal_" + app.utils.randomString(); const tabs = { "Fields and relations": erd, "Rules": rules, }; const data = store({ showSystemCollections: false, activeTab: Object.keys(tabs)[0], get collections() { if (data.showSystemCollections) { return app.store.collections; } return app.store.collections.filter((c) => !c.system); }, }); const modal = t.div( { pbEvent: "collectionsOverviewModal", className: "modal popup collections-overview-modal", onbeforeopen: (el) => { return settings.onbeforeopen?.(el); }, onafteropen: (el) => { settings.onafteropen?.(el); }, onbeforeclose: (el) => { return settings.onbeforeclose?.(el); }, onafterclose: (el) => { settings.onafterclose?.(el); el?.remove(); }, }, t.header( { className: "modal-header isolated" }, t.div( { className: "grid sm" }, t.div( { className: "col-12" }, t.div( { className: "flex" }, t.h6({ className: "modal-title" }, "Collections overview"), t.div({ className: "flex-fill" }), t.div( { className: "field" }, t.input({ id: uniqueId + ".showSystemCollections", type: "checkbox", className: "sm switch", checked: () => data.showSystemCollections, onchange: (e) => data.showSystemCollections = e.target.checked, }), t.label({ htmlFor: uniqueId + ".showSystemCollections" }, "System collections"), ), t.button( { type: "button", className: "btn sm secondary transparent circle modal-close-btn", title: "Close", onclick: () => app.modals.close(modal), }, t.i({ className: "ri-close-line", ariaHidden: true }), ), ), ), t.div( { className: "col-12" }, t.div( { className: "tabs-header equal-width" }, () => { const items = []; for (let title in tabs) { items.push(t.button({ type: "button", className: () => `tab-item ${data.activeTab == title ? "active" : ""}`, onclick: () => data.activeTab = title, textContent: title, })); } return items; }, ), ), ), ), () => { return tabs[data.activeTab]?.(data); }, ); return modal; } function erd(data) { return t.div( { className: "modal-content erd-tab" }, app.components.erd({ collections: () => { let underscoreA, underscoreB; function sortSystemUnderscoredLast(a, b) { underscoreA = a.name.startsWith("_"); underscoreB = b.name.startsWith("_"); if ( (a.system && !b.system) || (underscoreA && !underscoreB) ) { return 1; } if ( (!a.system && b.system) || (!underscoreA && underscoreB) ) { return -1; } return 0; } return data.collections.slice().sort(sortSystemUnderscoredLast); }, }), ); } function rules(data) { const ruleOptions = [ { value: "listRule", label: "List/Search rule" }, { value: "viewRule", label: "View rule" }, { value: "createRule", label: "Create rule", filter: (c) => c.type != "view" }, { value: "updateRule", label: "Update rule", filter: (c) => c.type != "view" }, { value: "deleteRule", label: "Delete rule", filter: (c) => c.type != "view" }, { value: "authRule", label: "Auth rule", filter: (c) => c.type == "auth" }, { value: "manageRule", label: "Manage rule", filter: (c) => c.type == "auth" }, { value: "mfaRule", label: "MFA rule", emptyLabel: t.span({ className: "label info" }, "Enabled for everyone"), rule: (c) => c.mfa?.rule, filter: (c) => c.mfa?.enabled && c.type == "auth", }, ]; const local = store({ activeRuleOption: ruleOptions[0], get activeCollections() { if (!local.activeRuleOption.filter) { return data.collections; } return data.collections.filter((c) => local.activeRuleOption.filter(c)); }, }); return t.div( { className: "modal-content rules-tab" }, t.table( { className: "rules-table" }, t.thead( { className: "sticky" }, t.tr( null, t.td( { colSpan: 99, className: "col-rule-btns" }, t.div( { className: "rule-btns" }, () => { return ruleOptions.map((opt) => { return t.button({ type: "button", className: () => `btn sm ${ local.activeRuleOption?.value == opt.value ? "outline" : "transparent secondary" }`, textContent: () => opt.label, onclick: () => local.activeRuleOption = opt, }); }); }, ), ), ), ), t.tbody( null, () => { if (!local.activeCollections.length) { return t.tr( null, t.td( { colSpan: 99, className: "txt-hint" }, t.p(null, "No collections with the selected rule."), ), ); } return local.activeCollections.map((collection) => { return t.tr( null, t.th( { className: "min-width" }, t.div( { className: "inline-flex gap-10" }, t.i({ ariaHidden: true, className: () => app.collectionTypes[collection.type]?.icon || app.utils.fallbackCollectionIcon, }), t.span({ className: "txt collection-name", title: () => collection.name, textContent: () => collection.name, }), ), ), () => { let rule; if (local.activeRuleOption.rule) { rule = local.activeRuleOption.rule(collection); } else { rule = collection[local.activeRuleOption.value]; } return t.td( { style: "vertical-align: top" }, () => { if (rule === null) { if (local.activeRuleOption.nullLabel) { return local.activeRuleOption.nullLabel; } return t.span({ className: "label success" }, "Superusers only"); } if (rule === "") { if (local.activeRuleOption.emptyLabel) { return local.activeRuleOption.emptyLabel; } return t.span({ className: "label info" }, "Public"); } return app.components.codeBlock({ language: "pbrule", value: rule, }); }, ); }, ); }); }, ), ), ); }