Add branch selection on extension installer Closes #3865

310b0f30cd5dce0a5d67a7666b42c1c231b34182

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

5 files changed, +54 -14Showing whitespace changes
public/scripts/RossAscends-mods.js+1 -1
@@ -1120,7 +1120,7 @@ export function initRossMods() {
1120 const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {1120 const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
1121 customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],1121 customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
1122 onClose: (popup) => {1122 onClose: (popup) => {
1123 regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false;1123 regenerateWithCtrlEnter = Boolean(popup.inputResults.get('regenerateWithCtrlEnter') ?? false);
1124 },1124 },
1125 });1125 });
1126 if (!result) {1126 if (!result) {
public/scripts/extensions.js+13 -3
@@ -1061,7 +1061,7 @@ async function getExtensionVersion(extensionName, abortSignal) {
1061 * @param {boolean} global Is the extension global?1061 * @param {boolean} global Is the extension global?
1062 * @returns {Promise<void>}1062 * @returns {Promise<void>}
1063 */1063 */
1064export async function installExtension(url, global) {1064export async function installExtension(url, global, branch = '') {
1065 console.debug('Extension installation started', url);1065 console.debug('Extension installation started', url);
10661066
1067 toastr.info(t`Please wait...`, t`Installing extension`);1067 toastr.info(t`Please wait...`, t`Installing extension`);
@@ -1072,6 +1072,7 @@ export async function installExtension(url, global) {
1072 body: JSON.stringify({1072 body: JSON.stringify({
1073 url,1073 url,
1074 global,1074 global,
1075 branch,
1075 }),1076 }),
1076 });1077 });
10771078
@@ -1406,9 +1407,17 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
1406 await popup.complete(POPUP_RESULT.AFFIRMATIVE);1407 await popup.complete(POPUP_RESULT.AFFIRMATIVE);
1407 },1408 },
1408 };1409 };
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
1410 const customButtons = isCurrentUserAdmin ? [installForAllButton] : [];1418 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 });
1412 const input = await popup.show();1421 const input = await popup.show();
14131422
1414 if (!input) {1423 if (!input) {
@@ -1417,7 +1426,8 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
1417 }1426 }
14181427
1419 const url = String(input).trim();1428 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);
1421}1431}
14221432
1423export async function initExtensions() {1433export async function initExtensions() {
public/scripts/extensions/assets/index.js+2 -2
@@ -291,7 +291,7 @@ async function installAsset(url, assetType, filename) {
291 try {291 try {
292 if (category === 'extension') {292 if (category === 'extension') {
293 console.debug(DEBUG_PREFIX, 'Installing extension ', url);293 console.debug(DEBUG_PREFIX, 'Installing extension ', url);
294 await installExtension(url);294 await installExtension(url, false);
295 console.debug(DEBUG_PREFIX, 'Extension installed.');295 console.debug(DEBUG_PREFIX, 'Extension installed.');
296 return;296 return;
297 }297 }
@@ -309,7 +309,7 @@ async function installAsset(url, assetType, filename) {
309 console.debug(DEBUG_PREFIX, 'Importing character ', filename);309 console.debug(DEBUG_PREFIX, 'Importing character ', filename);
310 const blob = await result.blob();310 const blob = await result.blob();
311 const file = new File([blob], filename, { type: blob.type });311 const file = new File([blob], filename, { type: blob.type });
312 await processDroppedFiles([file], true);312 await processDroppedFiles([file]);
313 console.debug(DEBUG_PREFIX, 'Character downloaded.');313 console.debug(DEBUG_PREFIX, 'Character downloaded.');
314 }314 }
315 }315 }
public/scripts/popup.js+31 -5
@@ -71,7 +71,8 @@ export const POPUP_RESULT = {
71 * @property {string} id - The id for the html element71 * @property {string} id - The id for the html element
72 * @property {string} label - The label text for the input72 * @property {string} label - The label text for the input
73 * @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label73 * @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
74 * @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set)74 * @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)
75 */76 */
7677
77/**78/**
@@ -157,7 +158,7 @@ export class Popup {
157158
158 /** @type {POPUP_RESULT|number} */ result;159 /** @type {POPUP_RESULT|number} */ result;
159 /** @type {any} */ value;160 /** @type {any} */ value;
160 /** @type {Map<string,boolean>?} */ inputResults;161 /** @type {Map<string,string|boolean>?} */ inputResults;
161 /** @type {any} */ cropData;162 /** @type {any} */ cropData;
162163
163 /** @type {HTMLElement} */ lastFocus;164 /** @type {HTMLElement} */ lastFocus;
@@ -260,13 +261,14 @@ export class Popup {
260 return;261 return;
261 }262 }
262263
264 if (!input.type || input.type === 'checkbox') {
263 const label = document.createElement('label');265 const label = document.createElement('label');
264 label.classList.add('checkbox_label', 'justifyCenter');266 label.classList.add('checkbox_label', 'justifyCenter');
265 label.setAttribute('for', input.id);267 label.setAttribute('for', input.id);
266 const inputElement = document.createElement('input');268 const inputElement = document.createElement('input');
267 inputElement.type = 'checkbox';269 inputElement.type = 'checkbox';
268 inputElement.id = input.id;270 inputElement.id = input.id;
269 inputElement.checked = input.defaultState ?? false;271 inputElement.checked = Boolean(input.defaultState ?? false);
270 label.appendChild(inputElement);272 label.appendChild(inputElement);
271 const labelText = document.createElement('span');273 const labelText = document.createElement('span');
272 labelText.innerText = input.label;274 labelText.innerText = input.label;
@@ -282,6 +284,29 @@ export class Popup {
282 }284 }
283285
284 this.inputControls.appendChild(label);286 this.inputControls.appendChild(label);
287 }
288
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 }
285 });310 });
286311
287 // Set the default button class312 // Set the default button class
@@ -529,7 +554,8 @@ export class Popup {
529 this.inputResults = new Map(this.customInputs.map(input => {554 this.inputResults = new Map(this.customInputs.map(input => {
530 /** @type {HTMLInputElement} */555 /** @type {HTMLInputElement} */
531 const inputControl = this.dlg.querySelector(`#${input.id}`);556 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];
533 }));559 }));
534 }560 }
535561
@@ -619,7 +645,7 @@ export class Popup {
619 /** @readonly @type {Popup[]} Remember all popups */645 /** @readonly @type {Popup[]} Remember all popups */
620 popups: [],646 popups: [],
621647
622 /** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, boolean>?}?} Last popup result */648 /** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, string|boolean>?}?} Last popup result */
623 lastResult: null,649 lastResult: null,
624650
625 /** @returns {boolean} Checks if any modal popup dialog is open */651 /** @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) => {
76 fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions);76 fs.mkdirSync(PUBLIC_DIRECTORIES.globalExtensions);
77 }77 }
7878
79 const { url, global } = request.body;79 const { url, global, branch } = request.body;
8080
81 if (global && !request.user.profile.admin) {81 if (global && !request.user.profile.admin) {
82 console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`);82 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) => {
90 return response.status(409).send(`Directory already exists at ${extensionPath}`);90 return response.status(409).send(`Directory already exists at ${extensionPath}`);
91 }91 }
9292
93 await git.clone(url, extensionPath, { '--depth': 1 });93 const cloneOptions = { '--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
96 const { version, author, display_name } = await getManifest(extensionPath);100 const { version, author, display_name } = await getManifest(extensionPath);
97101