Skip to content

Commit 0bf1dcc

Browse files
authored
Merge pull request #30 from chsami/chsami/scrollable_accounts
Chsami/scrollable accounts
2 parents 0b78bdd + 05da4ca commit 0bf1dcc

8 files changed

Lines changed: 317 additions & 8 deletions

File tree

css/styles.css

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,52 @@ h2 {
217217
width: 100%;
218218
}
219219

220+
/* Row wrapper to align dropdown and refresh button horizontally */
221+
.accounts-refresh-row {
222+
display: flex;
223+
align-items: stretch;
224+
gap: 6px;
225+
width: 100%;
226+
margin-bottom: 15px;
227+
}
228+
229+
.accounts-refresh-row>.accounts-dropdown-container {
230+
flex: 1;
231+
margin-bottom: 0;
232+
/* remove extra spacing inside row */
233+
}
234+
235+
/* Refresh accounts button styling */
236+
.refresh-accounts-btn {
237+
background-color: #1f2638;
238+
border: 1px solid #2f3950;
239+
border-radius: 8px;
240+
color: #00d4a4;
241+
cursor: pointer;
242+
display: flex;
243+
align-items: center;
244+
justify-content: center;
245+
font-size: 16px;
246+
min-width: 48px;
247+
padding: 0 12px;
248+
transition: background-color 0.2s ease, color 0.2s ease;
249+
}
250+
251+
.refresh-accounts-btn:hover:not(:disabled) {
252+
background-color: #243049;
253+
color: #35eac7;
254+
}
255+
256+
.refresh-accounts-btn:disabled {
257+
opacity: 0.5;
258+
cursor: not-allowed;
259+
}
260+
261+
.refresh-accounts-btn:focus-visible {
262+
outline: 2px solid #00d4a4;
263+
outline-offset: 2px;
264+
}
265+
220266
.account-select-hidden {
221267
display: none;
222268
}
@@ -814,4 +860,4 @@ button.hamburger {
814860

815861
.app-menu .submenu-item {
816862
font-size: 12px;
817-
}
863+
}

index.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,15 @@ <h2>New Update Available!</h2>
117117
<label for="client">Version</label>
118118
<select id="client"></select>
119119
<label for="character">Jagex Account</label>
120-
<div id="accounts-dropdown-container" class="accounts-dropdown-container" aria-live="polite"></div>
120+
<div class="accounts-refresh-row">
121+
<div id="accounts-dropdown-container" class="accounts-dropdown-container" aria-live="polite">
122+
</div>
123+
<button id="refresh-accounts" class="refresh-accounts-btn"
124+
title="Refresh the account list and update any outdated display names"
125+
aria-label="Refresh account list and update names">
126+
<span aria-hidden="true"></span>
127+
</button>
128+
</div>
121129
<select id="character" class="account-select-hidden" aria-hidden="true" tabindex="-1"></select>
122130
<label for="profile">Profile</label>
123131
<select id="profile">

libs/ipc-handlers.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,56 @@ module.exports = async function (deps) {
7070
await jarExecutorHandler(deps);
7171
const packageVersion = packageJson.version;
7272

73+
ipcMain.handle('refresh-accounts', async () => {
74+
try {
75+
const { writeAccountsToFile } = require(path.join(
76+
projectDir,
77+
'libs',
78+
'oauth-jagex.js'
79+
));
80+
81+
const accountsPath = path.join(microbotDir, 'accounts.json');
82+
if (!fs.existsSync(accountsPath)) {
83+
return { error: 'accounts.json does not exist' };
84+
}
85+
86+
let accountsData = [];
87+
try {
88+
const raw = fs.readFileSync(accountsPath, 'utf8');
89+
accountsData = JSON.parse(raw);
90+
} catch (err) {
91+
log.error('Failed to read accounts.json for refresh:', err.message);
92+
return { error: 'Failed to read accounts.json' };
93+
}
94+
95+
if (!Array.isArray(accountsData) || accountsData.length === 0) {
96+
return { error: 'No accounts found to refresh' };
97+
}
98+
99+
// Use first account's sessionId (assuming all accounts share the same session scope)
100+
const firstAccount = accountsData[0];
101+
const sessionId = firstAccount && firstAccount.sessionId;
102+
if (!sessionId) {
103+
return { error: 'No sessionId found in accounts.json' };
104+
}
105+
106+
await writeAccountsToFile(sessionId);
107+
108+
// Re-read accounts after refresh
109+
try {
110+
const updatedRaw = fs.readFileSync(accountsPath, 'utf8');
111+
const updatedAccounts = JSON.parse(updatedRaw);
112+
return { success: true, accounts: updatedAccounts };
113+
} catch (err) {
114+
log.error('Failed to read updated accounts.json after refresh:', err.message);
115+
return { success: true };
116+
}
117+
} catch (error) {
118+
log.error('Error refreshing accounts:', error.message);
119+
return { error: error.message };
120+
}
121+
});
122+
73123
ipcMain.handle('download-client', async (event, version) => {
74124
const url = `${filestorage}/releases/microbot/stable/microbot-${version}.jar`;
75125
try {

libs/oauth-jagex.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,7 @@ async function getToken(code) {
195195
return response.data.id_token;
196196
} catch (error) {
197197
log.error(
198-
`Error getting token: ${
199-
error.response ? error.response.data : error.message
198+
`Error getting token: ${error.response ? error.response.data : error.message
200199
}`
201200
);
202201
return null;
@@ -220,8 +219,7 @@ async function getSessionId(idToken) {
220219
return response.data.sessionId;
221220
} catch (error) {
222221
log.error(
223-
`Error getting session ID: ${
224-
error.response ? error.response.data : error.message
222+
`Error getting session ID: ${error.response ? error.response.data : error.message
225223
}`
226224
);
227225
return null;
@@ -418,4 +416,4 @@ async function startAuthFlow() {
418416
});
419417
}
420418

421-
module.exports = { startAuthFlow };
419+
module.exports = { startAuthFlow, writeAccountsToFile };

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "microbot-launcher",
3-
"version": "3.2.5",
3+
"version": "3.2.6",
44
"description": "Launcher for the microbot client",
55
"main": "main.js",
66
"scripts": {

preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ contextBridge.exposeInMainWorld('electron', {
7373
ipcRenderer.invoke('cleanup-unused-clients-jar', latestVersion),
7474
updateClientJarTTL: (version) =>
7575
ipcRenderer.invoke('update-client-jar-ttl', version),
76+
refreshAccounts: () => ipcRenderer.invoke('refresh-accounts'),
7677
ipcRenderer: {
7778
send: (channel, data) => ipcRenderer.send(channel, data),
7879
receive: (channel, func) =>

renderer.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
let accounts = [];
2+
let restoringSelectedAccount = false;
23
let iii = null;
34
let lastAccountsReadError = null;
45
let cleanupAccountsDropdownListeners = null;
@@ -110,6 +111,18 @@ async function safeReadAccounts() {
110111
}
111112

112113
lastAccountsReadError = null;
114+
// Sort alphabetically by displayName (case-insensitive); fallback to accountId
115+
try {
116+
result.sort((a, b) => {
117+
const nameA = (a?.displayName || a?.accountId || '').toString().trim().toLowerCase();
118+
const nameB = (b?.displayName || b?.accountId || '').toString().trim().toLowerCase();
119+
if (nameA < nameB) return -1;
120+
if (nameA > nameB) return 1;
121+
return 0;
122+
});
123+
} catch (_) {
124+
// Ignore sort errors
125+
}
113126
return result;
114127
}
115128

@@ -232,6 +245,8 @@ async function handleJagexAccountLogic(properties) {
232245
updateCharacterSelection(accounts[0].accountId);
233246
}
234247
}
248+
// Attempt to restore persisted selection after any file change
249+
await restoreSelectedAccountIfAny();
235250
}
236251
}, 1000);
237252
}
@@ -339,6 +354,9 @@ window.addEventListener('load', async () => {
339354
const profile = nonJagexProfile || 'default';
340355
document.getElementById('profile').value = profile;
341356
}
357+
if (!restoringSelectedAccount) {
358+
await persistSelectedAccount(selectedAccount);
359+
}
342360
});
343361

344362
//Init buttons and UI
@@ -864,6 +882,48 @@ function setupAddAccountsButton() {
864882
addAccountsButton?.addEventListener('click', addAccountsHandler);
865883
}
866884

885+
function setupRefreshAccountsButton() {
886+
const refreshBtn = document.getElementById('refresh-accounts');
887+
if (!refreshBtn) return;
888+
refreshBtn.addEventListener('click', async () => {
889+
refreshBtn.disabled = true;
890+
try {
891+
const result = await window.electron.refreshAccounts();
892+
if (result?.error) {
893+
window.electron.errorAlert(result.error);
894+
return;
895+
}
896+
const updated = result?.accounts;
897+
if (Array.isArray(updated)) {
898+
// Sort updated accounts alphabetically
899+
accounts = [...updated].sort((a, b) => {
900+
const nameA = (a?.displayName || a?.accountId || '').toString().trim().toLowerCase();
901+
const nameB = (b?.displayName || b?.accountId || '').toString().trim().toLowerCase();
902+
if (nameA < nameB) return -1;
903+
if (nameA > nameB) return 1;
904+
return 0;
905+
});
906+
await setupSidebarLayout(accounts.length);
907+
await updateProfileBasedOnCharacter();
908+
await restoreSelectedAccountIfAny();
909+
} else {
910+
// fallback: re-read accounts via existing flow
911+
const latestAccounts = await safeReadAccounts();
912+
if (latestAccounts) {
913+
accounts = latestAccounts; // already sorted in safeReadAccounts
914+
await setupSidebarLayout(accounts.length);
915+
await updateProfileBasedOnCharacter();
916+
await restoreSelectedAccountIfAny();
917+
}
918+
}
919+
} catch (err) {
920+
window.electron.errorAlert(err?.message || String(err));
921+
} finally {
922+
refreshBtn.disabled = false;
923+
}
924+
});
925+
}
926+
867927
/**
868928
* Extracts the version number from a string.
869929
* e.g., "microbot-1.9.6.1.jar" becomes "1.9.6.1"
@@ -1142,6 +1202,45 @@ async function updateProfileBasedOnCharacter() {
11421202
}
11431203
}
11441204

1205+
async function persistSelectedAccount(accountId) {
1206+
try {
1207+
const props = await window.electron.readProperties();
1208+
if (!accountId || accountId === 'none') {
1209+
if (props['selected_account']) {
1210+
delete props['selected_account'];
1211+
await window.electron.writeProperties(props);
1212+
}
1213+
return;
1214+
}
1215+
if (props['selected_account'] !== accountId) {
1216+
props['selected_account'] = accountId;
1217+
await window.electron.writeProperties(props);
1218+
}
1219+
} catch (err) {
1220+
window.electron.logError(`Failed to persist selected account: ${err?.message || err}`);
1221+
}
1222+
}
1223+
1224+
async function restoreSelectedAccountIfAny() {
1225+
try {
1226+
const props = await window.electron.readProperties();
1227+
const saved = props['selected_account'];
1228+
if (!saved) return;
1229+
const exists = accounts.some((a) => a.accountId === saved);
1230+
if (!exists) {
1231+
delete props['selected_account'];
1232+
await window.electron.writeProperties(props);
1233+
return;
1234+
}
1235+
restoringSelectedAccount = true;
1236+
updateCharacterSelection(saved, { suppressRender: true });
1237+
restoringSelectedAccount = false;
1238+
await updateProfileBasedOnCharacter();
1239+
} catch (err) {
1240+
window.electron.logError(`Failed to restore selected account: ${err?.message || err}`);
1241+
}
1242+
}
1243+
11451244
async function initUI(properties) {
11461245
updateNowBtn();
11471246
reminderMeLaterBtn();
@@ -1159,6 +1258,7 @@ async function initUI(properties) {
11591258
const accountsData = await safeReadAccounts();
11601259
accounts = accountsData ?? [];
11611260
await setupSidebarLayout(accounts.length);
1261+
await restoreSelectedAccountIfAny();
11621262

11631263
const orderedClientJars = await orderClientJarsByVersion();
11641264
populateSelectElement('client', orderedClientJars);
@@ -1175,6 +1275,8 @@ async function initUI(properties) {
11751275

11761276
await setupRamInput();
11771277
await setupProxyInput();
1278+
1279+
setupRefreshAccountsButton();
11781280
}
11791281

11801282
/**

0 commit comments

Comments
 (0)