import {SOURCE_UNKNOWN_ABBREVIATION, SOURCE_UNKNOWN_FULL} from "./utils-brew-constants.js"; import {BrewDoc} from "./utils-brew-models.js"; import {GetBrewUi} from "./utils-brew-ui-get.js"; import {ManageEditableBrewContentsUi} from "./utils-brew-ui-manage-editable-contents.js"; import {ManageExternalUtils} from "../manageexternal/manageexternal-utils.js"; export class ManageBrewUi { static _RenderState = class { constructor () { this.$stgBrewList = null; this.list = null; this.listSelectClickHandler = null; this.brews = []; this.menuListMass = null; this.rowMetas = []; } }; constructor ({brewUtil, isModal = false} = {}) { this._brewUtil = brewUtil; this._isModal = isModal; } /* -------------------------------------------- */ static _CONTEXT_MENU_BTNGROUP_MANAGER = null; static bindBtngroupManager (btngroup) { btngroup .first(`[name="manage-content"]`) .onn("click", evt => this._pOnClickBtnManageContent({evt})); btngroup .first(`[name="manage-prerelease"]`) .onn("click", evt => this._onClickBtnManagePrereleaseBrew({brewUtil: PrereleaseUtil, isGoToPage: evt.shiftKey})); btngroup .first(`[name="manage-brew"]`) .onn("click", evt => this._onClickBtnManagePrereleaseBrew({brewUtil: BrewUtil2, isGoToPage: evt.shiftKey})); } static bindBtnOpen ($btn, {brewUtil = null} = {}) { brewUtil = brewUtil || BrewUtil2; $btn.click(evt => this._onClickBtnManagePrereleaseBrew({brewUtil, isGoToPage: evt.shiftKey})); } static _pOnClickBtnManageContent ({evt}) { this._CONTEXT_MENU_BTNGROUP_MANAGER ||= ContextUtil.getMenu([ new ContextUtil.Action( "Manage Prerelease Content", async evt => { this._onClickBtnManagePrereleaseBrew({brewUtil: PrereleaseUtil, isGoToPage: evt.shiftKey}); }, ), new ContextUtil.Action( "Manage Homebrew", async evt => { this._onClickBtnManagePrereleaseBrew({brewUtil: BrewUtil2, isGoToPage: evt.shiftKey}); }, ), null, new ContextUtil.Action( "Load All Partnered Content", async evt => { await this.pOnClickBtnLoadAllPartnered(); }, ), null, new ContextUtil.Action( "Delete All Loaded Content", async evt => { await this._pOnClickBtnDeleteAllLoadedContent(); }, ), ]); return ContextUtil.pOpenMenu(evt, this._CONTEXT_MENU_BTNGROUP_MANAGER); } static _onClickBtnManagePrereleaseBrew ({brewUtil, isGoToPage}) { if (isGoToPage) return window.location = brewUtil.PAGE_MANAGE; return this.pDoManageBrew({brewUtil}); } static async pOnClickBtnLoadAllPartnered () { const brewDocs = []; try { const [brewDocsPrerelease, brewDocsHomebrew] = await Promise.all([ PrereleaseUtil.pAddBrewsPartnered({isSilent: true}), BrewUtil2.pAddBrewsPartnered({isSilent: true}), ]); brewDocs.push( ...brewDocsPrerelease, ...brewDocsHomebrew, ); } catch (e) { JqueryUtil.doToast({type: "danger", content: `Failed to load partnered content! ${VeCt.STR_SEE_CONSOLE}`}); throw e; } if (brewDocs.length) JqueryUtil.doToast(`Loaded partnered content!`); if (PrereleaseUtil.isReloadRequired()) PrereleaseUtil.doLocationReload(); if (BrewUtil2.isReloadRequired()) BrewUtil2.doLocationReload(); } static async _pOnClickBtnDeleteAllLoadedContent () { if ( !await InputUiUtil.pGetUserBoolean({ title: `Delete All Loaded ${PrereleaseUtil.DISPLAY_NAME.toTitleCase()} and ${BrewUtil2.DISPLAY_NAME.toTitleCase()}`, htmlDescription: `
Are you sure?
Note that this will not delete your Editable ${PrereleaseUtil.DISPLAY_NAME.toTitleCase()} and Editable ${BrewUtil2.DISPLAY_NAME.toTitleCase()}.
`, textYes: "Yes", textNo: "Cancel", }) ) return; try { await Promise.all([ PrereleaseUtil.pDeleteUneditableBrews(), BrewUtil2.pDeleteUneditableBrews(), ]); } catch (e) { JqueryUtil.doToast({type: "danger", content: `Failed to load partnered content! ${VeCt.STR_SEE_CONSOLE}`}); throw e; } if (PrereleaseUtil.isReloadRequired()) PrereleaseUtil.doLocationReload(); if (BrewUtil2.isReloadRequired()) BrewUtil2.doLocationReload(); } /* -------------------------------------------- */ static async pOnClickBtnExportListAsUrl ({ele}) { const url = await ManageExternalUtils.pGetUrl(); await MiscUtil.pCopyTextToClipboard(url); JqueryUtil.showCopiedEffect(ele); if ( !await PrereleaseUtil.pHasEditableSourceJson() && !await BrewUtil2.pHasEditableSourceJson() ) return; JqueryUtil.doToast({type: "warning", content: `Note: you have Editable ${PrereleaseUtil.DISPLAY_NAME} or ${BrewUtil2.DISPLAY_NAME}. This cannot be exported as part of a URL, and so was not included.`}); } /* -------------------------------------------- */ static async pDoManageBrew ({brewUtil = null} = {}) { brewUtil = brewUtil || BrewUtil2; const ui = new this({isModal: true, brewUtil}); const rdState = new this._RenderState(); const {$modalInner} = UiUtil.getShowModal({ isHeight100: true, isWidth100: true, title: `Manage ${brewUtil.DISPLAY_NAME.toTitleCase()}`, isUncappedHeight: true, $titleSplit: $$`
${ui._$getBtnPullAll(rdState)} ${ui._$getBtnDeleteAll(rdState)}
`, isHeaderBorder: true, cbClose: () => { if (!brewUtil.isReloadRequired()) return; brewUtil.doLocationReload(); }, }); await ui.pRender($modalInner, {rdState}); } _$getBtnDeleteAll (rdState) { const brewUtilOther = this._brewUtil === PrereleaseUtil ? BrewUtil2 : PrereleaseUtil; return $(``) .addClass(this._isModal ? "btn-xs" : "btn-sm") .click(async evt => { if (!evt.shiftKey) { if (!await InputUiUtil.pGetUserBoolean({title: `Delete All ${this._brewUtil.DISPLAY_NAME.toTitleCase()}`, htmlDescription: "Are you sure?", textYes: "Yes", textNo: "Cancel"})) return; await this._pDoDeleteAll(rdState); return; } if ( !await InputUiUtil.pGetUserBoolean({ title: `Delete All ${this._brewUtil.DISPLAY_NAME.toTitleCase()} and ${brewUtilOther.DISPLAY_NAME.toTitleCase()}`, htmlDescription: "Are you sure?", textYes: "Yes", textNo: "Cancel", }) ) return; await brewUtilOther.pSetBrew([]); await this._pDoDeleteAll(rdState); }); } _$getBtnPullAll (rdState) { const $btn = $(``) .addClass(this._isModal ? "btn-xs w-70p" : "btn-sm w-80p") .click(async () => { const cachedHtml = $btn.html(); try { $btn.text(`Updating...`).prop("disabled", true); await this._pDoPullAll({rdState}); } catch (e) { $btn.text(`Failed!`); setTimeout(() => $btn.html(cachedHtml).prop("disabled", false), VeCt.DUR_INLINE_NOTIFY); throw e; } $btn.text(`Done!`); setTimeout(() => $btn.html(cachedHtml).prop("disabled", false), VeCt.DUR_INLINE_NOTIFY); }); return $btn; } async _pDoDeleteAll (rdState) { await this._brewUtil.pSetBrew([]); rdState.list.removeAllItems(); rdState.list.update(); } async _pDoPullAll ({rdState, brews = null}) { if (brews && !brews.length) return; let cntPulls; try { cntPulls = await this._brewUtil.pPullAllBrews({brews}); } catch (e) { JqueryUtil.doToast({content: `Update failed! ${VeCt.STR_SEE_CONSOLE}`, type: "danger"}); throw e; } if (!cntPulls) return JqueryUtil.doToast(`Update complete! No ${this._brewUtil.DISPLAY_NAME} was updated.`); await this._pRender_pBrewList(rdState); JqueryUtil.doToast(`Update complete! ${cntPulls} ${cntPulls === 1 ? `${this._brewUtil.DISPLAY_NAME} was` : `${this._brewUtil.DISPLAY_NAME_PLURAL} were`} updated.`); } async pRender ($wrp, {rdState = null} = {}) { rdState = rdState || new this.constructor._RenderState(); rdState.$stgBrewList = $(`
`); await this._pRender_pBrewList(rdState); const btnLoadPartnered = ee`` .onn("click", () => this._pHandleClick_btnLoadPartnered(rdState)); const $btnLoadFromFile = $(``) .click(() => this._pHandleClick_btnLoadFromFile(rdState)); const $btnLoadFromUrl = $(``) .click(() => this._pHandleClick_btnLoadFromUrl(rdState)); const $btnGet = $(``) .click(() => this._pHandleClick_btnGetBrew(rdState)); const $btnCustomUrl = $(``) .click(() => this._pHandleClick_btnSetCustomRepo()); const $btnPullAll = this._isModal ? null : this._$getBtnPullAll(rdState); const $btnDeleteAll = this._isModal ? null : this._$getBtnDeleteAll(rdState); const $btnSaveToUrl = $(``) .click(async evt => { await this.constructor.pOnClickBtnExportListAsUrl({ele: evt.originalEvent.currentTarget}); }); const $wrpBtns = $$`
${$btnGet} ${$btnCustomUrl}
${btnLoadPartnered}
${$btnLoadFromFile} ${$btnLoadFromUrl}
${$btnSaveToUrl}
${$btnPullAll} ${$btnDeleteAll}
`; if (this._isModal) { $$($wrp)` ${rdState.$stgBrewList} ${$wrpBtns.addClass("mb-2")}`; } else { $$($wrp)` ${$wrpBtns.addClass("mb-3")} ${rdState.$stgBrewList}`; } } async _pHandleClick_btnLoadPartnered (rdState) { await this._brewUtil.pAddBrewsPartnered(); await this._pRender_pBrewList(rdState); } async _pHandleClick_btnLoadFromFile (rdState) { const {files, errors} = await InputUiUtil.pGetUserUploadJson({isMultiple: true}); DataUtil.doHandleFileLoadErrorsGeneric(errors); await this._brewUtil.pAddBrewsFromFiles(files); await this._pRender_pBrewList(rdState); } async _pHandleClick_btnLoadFromUrl (rdState) { const enteredUrl = await InputUiUtil.pGetUserString({title: `${this._brewUtil.DISPLAY_NAME.toTitleCase()} URL`}); if (!enteredUrl || !enteredUrl.trim()) return; const parsedUrl = this.constructor._getParsedCustomUrl(enteredUrl); if (!parsedUrl) { return JqueryUtil.doToast({ content: `The URL was not valid!`, type: "danger", }); } await this._brewUtil.pAddBrewFromUrl(parsedUrl.href); await this._pRender_pBrewList(rdState); } static _getParsedCustomUrl (enteredUrl) { try { return new URL(enteredUrl); } catch (e) { return null; } } async _pHandleClick_btnGetBrew (rdState) { await GetBrewUi.pDoGetBrew({brewUtil: this._brewUtil, isModal: this._isModal}); await this._pRender_pBrewList(rdState); } async _pHandleClick_btnSetCustomRepo () { const customBrewUtl = await this._brewUtil.pGetCustomUrl(); const nxtUrl = await InputUiUtil.pGetUserString({ title: `${this._brewUtil.DISPLAY_NAME.toTitleCase()} Repository URL`, $elePre: $(`

Leave blank to use the default ${this._brewUtil.DISPLAY_NAME} repo.

Note that for GitHub URLs, the raw. URL must be used. For example, ${this._brewUtil.URL_REPO_ROOT_DEFAULT.replace(/TheGiddyLimit/g, "YourUsernameHere")}

`), default: customBrewUtl, }); if (nxtUrl == null) return; await this._brewUtil.pSetCustomUrl(nxtUrl); } async _pRender_pBrewList (rdState) { rdState.$stgBrewList.empty(); rdState.rowMetas.splice(0, rdState.rowMetas.length) .forEach(({menu}) => ContextUtil.deleteMenu(menu)); const $btnMass = $(``) .click(evt => this._pHandleClick_btnListMass({evt, rdState})); const $iptSearch = $(``); const $cbAll = $(``); const $wrpList = $(`
`); rdState.list = new List({ $iptSearch, $wrpList, isUseJquery: true, isFuzzy: true, sortByInitial: rdState.list ? rdState.list.sortBy : undefined, sortDirInitial: rdState.list ? rdState.list.sortDir : undefined, }); const $wrpBtnsSort = $$`
`; $$(rdState.$stgBrewList)`
${$btnMass} ${$iptSearch}
${$wrpBtnsSort}
${$wrpList}
`; rdState.listSelectClickHandler = new ListSelectClickHandler({list: rdState.list}); rdState.listSelectClickHandler.bindSelectAllCheckbox($cbAll); SortUtil.initBtnSortHandlers($wrpBtnsSort, rdState.list); rdState.brews = (await this._brewUtil.pGetBrew()).map(brew => this._pRender_getProcBrew(brew)); rdState.brews.forEach((brew, ix) => { const meta = this._pRender_getLoadedRowMeta(rdState, brew, ix); rdState.rowMetas.push(meta); rdState.list.addItem(meta.listItem); }); rdState.list.init(); $iptSearch.focus(); } get _LBL_LIST_UPDATE () { return "Update"; } get _LBL_LIST_MANAGE_CONTENTS () { return "Manage Contents"; } get _LBL_LIST_EXPORT () { return "Export"; } get _LBL_LIST_VIEW_JSON () { return "View JSON"; } get _LBL_LIST_DELETE () { return "Delete"; } get _LBL_LIST_MOVE_TO_EDITABLE () { return `Move to Editable ${this._brewUtil.DISPLAY_NAME.toTitleCase()} Document`; } _initListMassMenu ({rdState}) { if (rdState.menuListMass) return; const getSelBrews = ({fnFilter = null} = {}) => { const brews = rdState.list.items .filter(li => li.data.cbSel.checked) .map(li => rdState.brews[li.ix]) .filter(brew => fnFilter ? fnFilter(brew) : true); if (!brews.length) JqueryUtil.doToast({content: `Please select some suitable ${this._brewUtil.DISPLAY_NAME_PLURAL} first!`, type: "warning"}); return brews; }; rdState.menuListMass = ContextUtil.getMenu([ new ContextUtil.Action( this._LBL_LIST_UPDATE, async () => this._pDoPullAll({ rdState, brews: getSelBrews(), }), ), new ContextUtil.Action( this._LBL_LIST_EXPORT, async () => { for (const brew of getSelBrews()) await this._pRender_pDoDownloadBrew({brew}); }, ), this._brewUtil.IS_EDITABLE ? new ContextUtil.Action( this._LBL_LIST_MOVE_TO_EDITABLE, async () => this._pRender_pDoMoveToEditable({ rdState, brews: getSelBrews({ fnFilter: brew => this._isBrewOperationPermitted_moveToEditable(brew), }), }), ) : null, new ContextUtil.Action( this._LBL_LIST_DELETE, async () => this._pRender_pDoDelete({ rdState, brews: getSelBrews({ fnFilter: brew => this._isBrewOperationPermitted_delete(brew), }), }), ), ].filter(Boolean)); } _isBrewOperationPermitted_update (brew) { return this._brewUtil.isPullable(brew); } _isBrewOperationPermitted_moveToEditable (brew) { return BrewDoc.isOperationPermitted_moveToEditable({brew}); } _isBrewOperationPermitted_delete (brew) { return !brew.head.isLocal; } async _pHandleClick_btnListMass ({evt, rdState}) { this._initListMassMenu({rdState}); await ContextUtil.pOpenMenu(evt, rdState.menuListMass); } static _getBrewName (brew) { const sources = brew.body._meta?.sources || []; return sources .map(brewSource => brewSource.full || SOURCE_UNKNOWN_FULL) .sort(SortUtil.ascSortLower) .join(", "); } _pRender_getLoadedRowMeta (rdState, brew, ix) { const sources = brew.body._meta?.sources || []; const rowsSubMetas = sources .map(brewSource => { const hasConverters = !!brewSource.convertedBy?.length; const btnConvertedBy = e_({ tag: "button", clazz: `btn btn-xxs btn-default ${!hasConverters ? "disabled" : ""}`, title: hasConverters ? `Converted by: ${brewSource.convertedBy.join(", ").qq()}` : "(No conversion credit given)", children: [ e_({tag: "span", clazz: "mobile__hidden", text: "View Converters"}), e_({tag: "span", clazz: "mobile__visible", text: "Convs.", title: "View Converters"}), ], click: () => { if (!hasConverters) return; const {$modalInner} = UiUtil.getShowModal({ title: `Converted By:${brewSource.convertedBy.length === 1 ? ` ${brewSource.convertedBy.join("")}` : ""}`, isMinHeight0: true, }); if (brewSource.convertedBy.length === 1) return; $modalInner.append(``); }, }); const authorsFull = [(brewSource.authors || [])].flat(2).join(", "); const lnkUrl = brewSource.url ? e_({ tag: "a", clazz: "ve-col-2 ve-text-center", href: brewSource.url, attrs: { target: "_blank", rel: "noopener noreferrer", }, text: "View Source", }) : e_({ tag: "span", clazz: "ve-col-2 ve-text-center", }); const eleRow = e_({ tag: "div", clazz: `w-100 ve-flex-v-center`, children: [ e_({ tag: "span", clazz: `ve-col-4 manbrew__source px-1`, text: brewSource.full, }), e_({ tag: "span", clazz: `ve-col-4 px-1`, text: authorsFull, }), lnkUrl, e_({ tag: "div", clazz: `ve-flex-vh-center ve-grow`, children: [ btnConvertedBy, ], }), ], }); return { eleRow, authorsFull, name: brewSource.full || SOURCE_UNKNOWN_FULL, abbreviation: brewSource.abbreviation || SOURCE_UNKNOWN_ABBREVIATION, }; }) .sort((a, b) => SortUtil.ascSortLower(a.name, b.name)); const brewName = this.constructor._getBrewName(brew); // region These are mutually exclusive const btnPull = this._pRender_getBtnPull({rdState, brew}); const btnEdit = this._pRender_getBtnEdit({rdState, brew}); const btnPullEditPlaceholder = (btnPull || btnEdit) ? null : this.constructor._pRender_getBtnPlaceholder(); // endregion const btnDownload = e_({ tag: "button", clazz: `btn btn-default btn-xs mobile__hidden w-24p`, title: this._LBL_LIST_EXPORT, children: [ e_({ tag: "span", clazz: "glyphicon glyphicon-download manbrew-row__icn-btn", }), ], click: () => this._pRender_pDoDownloadBrew({brew, brewName}), }); const btnViewJson = e_({ tag: "button", clazz: `btn btn-default btn-xs mobile-lg__hidden w-24p`, title: `${this._LBL_LIST_VIEW_JSON}: ${this.constructor._getBrewJsonTitle({brew, brewName})}`, children: [ e_({ tag: "span", clazz: "ve-bolder code relative manbrew-row__icn-btn--text", text: "{}", }), ], click: evt => this._pRender_doViewBrew({evt, brew, brewName}), }); const btnOpenMenu = e_({ tag: "button", clazz: `btn btn-default btn-xs w-24p`, title: "Menu", children: [ e_({ tag: "span", clazz: "glyphicon glyphicon-option-vertical manbrew-row__icn-btn", }), ], click: evt => this._pRender_pDoOpenBrewMenu({evt, rdState, brew, brewName, rowMeta}), }); const btnDelete = this._isBrewOperationPermitted_delete(brew) ? e_({ tag: "button", clazz: `btn btn-danger btn-xs mobile__hidden w-24p`, title: this._LBL_LIST_DELETE, children: [ e_({ tag: "span", clazz: "glyphicon glyphicon-trash manbrew-row__icn-btn", }), ], click: () => this._pRender_pDoDelete({rdState, brews: [brew]}), }) : this.constructor._pRender_getBtnPlaceholder(); // Weave in HRs const elesSub = rowsSubMetas.map(it => it.eleRow); for (let i = rowsSubMetas.length - 1; i > 0; --i) elesSub.splice(i, 0, e_({tag: "hr", clazz: `hr-1 hr--dotted`})); const cbSel = e_({ tag: "input", clazz: "no-events", type: "checkbox", }); const ptCategory = brew.head.isLocal ? {short: `Local`, title: `Local Document`} : brew.head.isEditable ? {short: `Editable`, title: `Editable Document`} : {short: `Standard`, title: `Standard Document`}; const eleLi = e_({ tag: "div", clazz: `manbrew__row ve-flex-v-center lst__row lst--border lst__row-inner no-shrink py-1 no-select`, children: [ e_({ tag: "label", clazz: `ve-col-0-5 ve-flex-vh-center ve-self-flex-stretch`, children: [cbSel], }), e_({ tag: "div", clazz: `ve-col-1 ve-text-center italic mobile__text-clip-ellipsis`, title: ptCategory.title, text: ptCategory.short, }), e_({ tag: "div", clazz: `ve-col-9 ve-flex-col`, children: elesSub, }), e_({ tag: "div", clazz: `ve-col-1-5 btn-group ve-flex-vh-center`, children: [ btnPull, btnEdit, btnPullEditPlaceholder, btnDownload, btnViewJson, btnOpenMenu, btnDelete, ], }), ], }); const listItem = new ListItem( ix, eleLi, brewName, { authors: rowsSubMetas.map(it => it.authorsFull).join(", "), abbreviation: rowsSubMetas.map(it => it.abbreviation).join(", "), }, { cbSel, }, ); eleLi.addEventListener("click", evt => rdState.listSelectClickHandler.handleSelectClick(listItem, evt, {isPassThroughEvents: true})); const rowMeta = { listItem, menu: null, }; return rowMeta; } static _pRender_getBtnPlaceholder () { return e_({ tag: "button", clazz: `btn btn-default btn-xs mobile__hidden w-24p`, html: " ", }) .attr("disabled", true); } _pRender_getBtnPull ({rdState, brew}) { if (!this._isBrewOperationPermitted_update(brew)) return null; const btnPull = e_({ tag: "button", clazz: `btn btn-default btn-xs mobile__hidden w-24p`, title: this._LBL_LIST_UPDATE, children: [ e_({ tag: "span", clazz: "glyphicon glyphicon-refresh manbrew-row__icn-btn", }), ], click: () => this._pRender_pDoPullBrew({rdState, brew}), }); if (!this._brewUtil.isPullable(brew)) btnPull.attr("disabled", true).attr("title", `(Update disabled\u2014no URL available)`); return btnPull; } _pRender_getBtnEdit ({rdState, brew}) { if (!brew.head.isEditable) return null; return e_({ tag: "button", clazz: `btn btn-default btn-xs mobile__hidden w-24p`, title: this._LBL_LIST_MANAGE_CONTENTS, children: [ e_({ tag: "span", clazz: "glyphicon glyphicon-pencil manbrew-row__icn-btn", }), ], click: () => this._pRender_pDoEditBrew({rdState, brew}), }); } async _pRender_pDoPullBrew ({rdState, brew}) { const isPull = await this._brewUtil.pPullBrew(brew); JqueryUtil.doToast( isPull ? `${this._brewUtil.DISPLAY_NAME.uppercaseFirst()} updated!` : `${this._brewUtil.DISPLAY_NAME.uppercaseFirst()} is already up-to-date.`, ); if (!isPull) return; await this._pRender_pBrewList(rdState); } async _pRender_pDoEditBrew ({rdState, brew}) { const {isDirty, brew: nxtBrew} = await ManageEditableBrewContentsUi.pDoOpen({brewUtil: this._brewUtil, brew, isModal: this._isModal}); if (!isDirty) return; await this._brewUtil.pUpdateBrew(nxtBrew); await this._pRender_pBrewList(rdState); } async _pRender_pDoDownloadBrew ({brew, brewName = null}) { const filename = (brew.head.filename || "").split(".").slice(0, -1).join("."); // For the editable brew, if there are multiple sources, present the user with a selection screen. We then filter // the editable brew down to whichever sources they selected. const isChooseSources = brew.head.isEditable && (brew.body._meta?.sources || []).length > 1; if (!isChooseSources) { const outFilename = filename || brewName || this.constructor._getBrewName(brew); const json = brew.head.isEditable ? MiscUtil.copyFast(brew.body) : brew.body; this.constructor._mutExportableEditableData({json: json}); return DataUtil.userDownload(outFilename, json, {isSkipAdditionalMetadata: true}); } // region Get chosen sources const getSourceAsText = source => `[${(source.abbreviation || "").qq()}] ${(source.full || "").qq()}`; const choices = await InputUiUtil.pGetUserMultipleChoice({ title: `Choose Sources`, values: brew.body._meta.sources, fnDisplay: getSourceAsText, isResolveItems: true, max: Number.MAX_SAFE_INTEGER, isSearchable: true, fnGetSearchText: getSourceAsText, }); if (choices == null || choices.length === 0) return; // endregion // region Filter output by selected sources const cpyBrew = MiscUtil.copyFast(brew.body); const sourceAllowlist = new Set(choices.map(it => it.json)); cpyBrew._meta.sources = cpyBrew._meta.sources.filter(it => sourceAllowlist.has(it.json)); Object.entries(cpyBrew) .forEach(([k, v]) => { if (!v || !(v instanceof Array)) return; if (k.startsWith("_")) return; cpyBrew[k] = v.filter(it => { const source = SourceUtil.getEntitySource(it); if (!source) return true; return sourceAllowlist.has(source); }); }); // endregion const reducedFilename = filename || this.constructor._getBrewName({body: cpyBrew}); this.constructor._mutExportableEditableData({json: cpyBrew}); return DataUtil.userDownload(reducedFilename, cpyBrew, {isSkipAdditionalMetadata: true}); } /** * The editable brew may contain `uniqueId` references from the builder, which should be stripped before export. */ static _mutExportableEditableData ({json}) { Object.values(json) .forEach(arr => { if (arr == null || !(arr instanceof Array)) return; arr.forEach(ent => delete ent.uniqueId); }); return json; } static _getBrewJsonTitle ({brew, brewName}) { brewName = brewName || this._getBrewName(brew); return brew.head.filename || brewName; } _pRender_doViewBrew ({evt, brew, brewName}) { const title = this.constructor._getBrewJsonTitle({brew, brewName}); const $content = Renderer.hover.$getHoverContent_statsCode(brew.body, {isSkipClean: true, title}); Renderer.hover.getShowWindow( $content, Renderer.hover.getWindowPositionFromEvent(evt), { title, isPermanent: true, isBookContent: true, }, ); } async _pRender_pDoOpenBrewMenu ({evt, rdState, brew, brewName, rowMeta}) { rowMeta.menu = rowMeta.menu || this._pRender_getBrewMenu({rdState, brew, brewName}); await ContextUtil.pOpenMenu(evt, rowMeta.menu); } _pRender_getBrewMenu ({rdState, brew, brewName}) { const menuItems = []; if (this._isBrewOperationPermitted_update(brew)) { menuItems.push( new ContextUtil.Action( this._LBL_LIST_UPDATE, async () => this._pRender_pDoPullBrew({rdState, brew}), ), ); } else if (brew.head.isEditable) { menuItems.push( new ContextUtil.Action( this._LBL_LIST_MANAGE_CONTENTS, async () => this._pRender_pDoEditBrew({rdState, brew}), ), ); } menuItems.push( new ContextUtil.Action( this._LBL_LIST_EXPORT, async () => this._pRender_pDoDownloadBrew({brew, brewName}), ), new ContextUtil.Action( this._LBL_LIST_VIEW_JSON, async evt => this._pRender_doViewBrew({evt, brew, brewName}), ), ); if (this._brewUtil.IS_EDITABLE && this._isBrewOperationPermitted_moveToEditable(brew)) { menuItems.push( new ContextUtil.Action( this._LBL_LIST_MOVE_TO_EDITABLE, async () => this._pRender_pDoMoveToEditable({rdState, brews: [brew]}), ), ); } if (this._isBrewOperationPermitted_delete(brew)) { menuItems.push( new ContextUtil.Action( this._LBL_LIST_DELETE, async () => this._pRender_pDoDelete({rdState, brews: [brew]}), ), ); } return ContextUtil.getMenu(menuItems); } _pGetUserBoolean_isMoveBrewsToEditable ({brews}) { return InputUiUtil.pGetUserBoolean({ title: `Move to Editable ${this._brewUtil.DISPLAY_NAME.toTitleCase()} Document`, htmlDescription: `Moving ${brews.length === 1 ? `this ${this._brewUtil.DISPLAY_NAME}` : `these ${this._brewUtil.DISPLAY_NAME_PLURAL}`} to the editable document will prevent ${brews.length === 1 ? "it" : "them"} from being automatically updated in future.
Are you sure you want to move ${brews.length === 1 ? "it" : "them"}?`, textYes: "Yes", textNo: "Cancel", }); } async _pRender_pDoMoveToEditable ({rdState, brews}) { if (!brews?.length) return; if (!await this._pGetUserBoolean_isMoveBrewsToEditable({brews})) return; await this._brewUtil.pMoveToEditable({brews}); await this._pRender_pBrewList(rdState); JqueryUtil.doToast(`${`${brews.length === 1 ? this._brewUtil.DISPLAY_NAME : this._brewUtil.DISPLAY_NAME_PLURAL}`.uppercaseFirst()} moved to editable document!`); } _pGetUserBoolean_isDeleteBrews ({brews}) { if (!brews.some(brew => brew.head.isEditable)) return true; const htmlDescription = brews.length === 1 ? `This document contains all your locally-created or edited ${this._brewUtil.DISPLAY_NAME_PLURAL}.
Are you sure you want to delete it?` : `One of the documents you are about to delete contains all your locally-created or edited ${this._brewUtil.DISPLAY_NAME_PLURAL}.
Are you sure you want to delete these documents?`; return InputUiUtil.pGetUserBoolean({ title: `Delete ${this._brewUtil.DISPLAY_NAME}`, htmlDescription, textYes: "Yes", textNo: "Cancel", }); } async _pRender_pDoDelete ({rdState, brews}) { if (!brews?.length) return; if (!await this._pGetUserBoolean_isDeleteBrews({brews})) return; await this._brewUtil.pDeleteBrews(brews); await this._pRender_pBrewList(rdState); } _pRender_getProcBrew (brew) { brew = MiscUtil.copyFast(brew); brew.body._meta.sources.sort((a, b) => SortUtil.ascSortLower(a.full || "", b.full || "")); return brew; } }