Add branch selection on extension installer Closes #3865

310b0f30cd5dce0a5d67a7666b42c1c231b34182

Cohee <18619528+Cohee1207@users.noreply.github.com>

5 files changed, +73 -33Ignore whitespace
public/scripts/RossAscends-mods.js+1 -1
@@ -1120,7 +1120,7 @@ export function initRossMods() {
11201120 const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
11211121 customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
11221122 onClose: (popup) => {
11231123 regenerateWithCtrlEnter = Boolean(popup.inputResults.get('regenerateWithCtrlEnter') ?? false);
11241124 },
11251125 });
11261126 if (!result) {
public/scripts/extensions.js+13 -3
@@ -1061,7 +1061,7 @@ async function getExtensionVersion(extensionName, abortSignal) {
10611061 * @param {boolean} global Is the extension global?
10621062 * @returns {Promise<void>}
10631063 */
10641064export async function installExtension(url, global, branch = '') {
10651065 console.debug('Extension installation started', url);
10661066
10671067 toastr.info(t`Please wait...`, t`Installing extension`);
@@ -1072,6 +1072,7 @@ export async function installExtension(url, global) {
10721072 body: JSON.stringify({
10731073 url,
10741074 global,
1075+ branch,
10751076 }),
10761077 });
10771078
@@ -1406,9 +1407,17 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
14061407 await popup.complete(POPUP_RESULT.AFFIRMATIVE);
14071408 },
14081409 };
1410+ /** @type {import('./popup.js').CustomPopupInput} */
1411+ const branchNameInput = {
1412+ id: 'extension_branch_name',
1413+ label: t`Branch name (optional)`,
1414+ type: 'text',
1415+ tooltip: 'e.g. main, master, dev',
1416+ };
14091417
14101418 const customButtons = isCurrentUserAdmin ? [installForAllButton] : [];
1411- const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons });
1419+ const customInputs = [branchNameInput];
1420+ const popup = new Popup(html, POPUP_TYPE.INPUT, suggestUrl ?? '', { okButton, customButtons, customInputs });
14121421 const input = await popup.show();
14131422
14141423 if (!input) {
@@ -1417,7 +1426,8 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
14171426 }
14181427
14191428 const url = String(input).trim();
1420- await installExtension(url, global);
1429+ const branchName = String(popup.inputResults.get('extension_branch_name') ?? '').trim();
1430+ await installExtension(url, global, branchName);
14211431}
14221432
14231433export async function initExtensions() {
public/scripts/extensions/assets/index.js+2 -2
@@ -291,7 +291,7 @@ async function installAsset(url, assetType, filename) {
291291 try {
292292 if (category === 'extension') {
293293 console.debug(DEBUG_PREFIX, 'Installing extension ', url);
294294 await installExtension(url, false);
295295 console.debug(DEBUG_PREFIX, 'Extension installed.');
296296 return;
297297 }
@@ -309,7 +309,7 @@ async function installAsset(url, assetType, filename) {
309309 console.debug(DEBUG_PREFIX, 'Importing character ', filename);
310310 const blob = await result.blob();
311311 const file = new File([blob], filename, { type: blob.type });
312312 await processDroppedFiles([file], true);
313313 console.debug(DEBUG_PREFIX, 'Character downloaded.');
314314 }
315315 }
public/scripts/popup.js+50 -24
@@ -71,7 +71,8 @@ export const POPUP_RESULT = {
7171 * @property {string} id - The id for the html element
7272 * @property {string} label - The label text for the input
7373 * @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
7474 * @property {boolean?|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set)
75+ * @property {string?} [type='checkbox'] - The type of the input (default is checkbox)
7576 */
7677
7778/**
@@ -157,7 +158,7 @@ export class Popup {
157158
158159 /** @type {POPUP_RESULT|number} */ result;
159160 /** @type {any} */ value;
160161 /** @type {Map<string,string|boolean>?} */ inputResults;
161162 /** @type {any} */ cropData;
162163
163164 /** @type {HTMLElement} */ lastFocus;
@@ -260,28 +261,52 @@ export class Popup {
260261 return;
261262 }
262263
263- const label = document.createElement('label');
264+ if (!input.type || input.type === 'checkbox') {
264- label.classList.add('checkbox_label', 'justifyCenter');
265+ const label = document.createElement('label');
265266 label.setAttributeclassList.add('forcheckbox_label', input.id'justifyCenter');
266- const inputElement = document.createElement('input');
267+ label.setAttribute('for', input.id);
267268 const inputElement.type = document.createElement('checkboxinput');
268269 inputElement.idtype = input.id'checkbox';
269270 inputElement.checkedid = input.defaultState ?? falseid;
270- label.appendChild(inputElement);
271+ inputElement.checked = Boolean(input.defaultState ?? false);
271- const labelText = document.createElement('span');
272+ label.appendChild(inputElement);
272- labelText.innerText = input.label;
273+ const labelText = document.createElement('span');
273274 labelText.dataset.i18ninnerText = input.label;
274- label.appendChild(labelText);
275+ labelText.dataset.i18n = input.label;
275-
276+ label.appendChild(labelText);
276- if (input.tooltip) {
277+
277- const tooltip = document.createElement('div');
278+ if (input.tooltip) {
278- tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
279+ const tooltip = document.createElement('div');
279- tooltip.title = input.tooltip;
280+ tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
280281 tooltip.dataset.i18n = '[title]' += input.tooltip;
281- label.appendChild(tooltip);
282+ tooltip.dataset.i18n = '[title]' + input.tooltip;
283+ label.appendChild(tooltip);
284+ }
285+
286+ this.inputControls.appendChild(label);
282287 }
283288
284- this.inputControls.appendChild(label);
289+ if (input.type === 'text') {
290+ const label = document.createElement('label');
291+ label.classList.add('text_label', 'justifyCenter');
292+ label.setAttribute('for', input.id);
293+
294+ const inputElement = document.createElement('input');
295+ inputElement.classList.add('text_pole');
296+ inputElement.type = 'text';
297+ inputElement.id = input.id;
298+ inputElement.value = String(input.defaultState ?? '');
299+ inputElement.placeholder = input.tooltip ?? '';
300+
301+ const labelText = document.createElement('span');
302+ labelText.innerText = input.label;
303+ labelText.dataset.i18n = input.label;
304+
305+ label.appendChild(labelText);
306+ label.appendChild(inputElement);
307+
308+ this.inputControls.appendChild(label);
309+ }
285310 });
286311
287312 // Set the default button class
@@ -529,7 +554,8 @@ export class Popup {
529554 this.inputResults = new Map(this.customInputs.map(input => {
530555 /** @type {HTMLInputElement} */
531556 const inputControl = this.dlg.querySelector(`#${input.id}`);
532- return [inputControl.id, inputControl.checked];
557+ const value = input.type === 'text' ? inputControl.value : inputControl.checked;
558+ return [inputControl.id, value];
533559 }));
534560 }
535561
@@ -619,7 +645,7 @@ export class Popup {
619645 /** @readonly @type {Popup[]} Remember all popups */
620646 popups: [],
621647
622648 /** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, string|boolean>?}?} Last popup result */
623649 lastResult: null,
624650
625651 /** @returns {boolean} Checks if any modal popup dialog is open */
src/endpoints/extensions.js+7 -3
@@ -76,7 +76,7 @@ router.post('/install', async (request, response) => {
7676 fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions);
7777 }
7878
7979 const { url, global, branch } = request.body;
8080
8181 if (global && !request.user.profile.admin) {
8282 console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`);
@@ -90,8 +90,12 @@ router.post('/install', async (request, response) => {
9090 return response.status(409).send(`Directory already exists at ${extensionPath}`);
9191 }
9292
9393 awaitconst git.clone(url,cloneOptions extensionPath,= { '--depth': 1 });
94- console.info(`Extension has been cloned at ${extensionPath}`);
94+ if (branch) {
95+ cloneOptions['--branch'] = branch;
96+ }
97+ await git.clone(url, extensionPath, cloneOptions);
98+ console.info(`Extension has been cloned to ${extensionPath} from ${url} at ${branch || '(default)'} branch`);
9599
96100 const { version, author, display_name } = await getManifest(extensionPath);
97101