| 1 |
|
| 2 |
* GoingsOn - Settings Module |
| 3 |
* Settings page with sidebar navigation, plugin manager |
| 4 |
|
| 5 |
|
| 6 |
(function() { |
| 7 |
'use strict'; |
| 8 |
const esc = GoingsOn.utils.escapeHtml; |
| 9 |
const escAttr = GoingsOn.utils.escapeAttr; |
| 10 |
|
| 11 |
let currentSection = 'appearance'; |
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
* Open settings as a modeless overlay above the current view. The |
| 17 |
* underlying app stays mounted so theme/notification/account changes |
| 18 |
* preview live against the real surfaces. (Phase 7 Tier 6.) |
| 19 |
|
| 20 |
async function openSettings() { |
| 21 |
const overlay = document.getElementById('settings-overlay'); |
| 22 |
if (!overlay) return; |
| 23 |
overlay.classList.remove('hidden'); |
| 24 |
overlay.setAttribute('aria-hidden', 'false'); |
| 25 |
document.addEventListener('keydown', handleSettingsKeydown); |
| 26 |
await showSection(currentSection); |
| 27 |
} |
| 28 |
|
| 29 |
|
| 30 |
* Called by navigation.loadViewData when settings view becomes active. |
| 31 |
* Renders the last-active section. Retained for back-compat with any |
| 32 |
* remaining callers; new code should call openSettings. |
| 33 |
|
| 34 |
async function loadSettings() { |
| 35 |
await showSection(currentSection); |
| 36 |
} |
| 37 |
|
| 38 |
|
| 39 |
* Close the settings overlay. Returns the user to whatever view was |
| 40 |
* underneath — no view switch needed since settings is now an overlay, |
| 41 |
* not a navigated subview. |
| 42 |
|
| 43 |
function goBack() { |
| 44 |
const overlay = document.getElementById('settings-overlay'); |
| 45 |
if (!overlay) return; |
| 46 |
overlay.classList.add('hidden'); |
| 47 |
overlay.setAttribute('aria-hidden', 'true'); |
| 48 |
document.removeEventListener('keydown', handleSettingsKeydown); |
| 49 |
} |
| 50 |
|
| 51 |
function handleSettingsKeydown(e) { |
| 52 |
if (e.key !== 'Escape') return; |
| 53 |
const isTyping = ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName) |
| 54 |
|| e.target.isContentEditable; |
| 55 |
if (isTyping) return; |
| 56 |
goBack(); |
| 57 |
} |
| 58 |
|
| 59 |
|
| 60 |
* Show a specific settings section and update sidebar active state. |
| 61 |
* @param {string} section - Section name (appearance, notifications, planning, plugins, sync, data) |
| 62 |
|
| 63 |
async function showSection(section) { |
| 64 |
currentSection = section; |
| 65 |
|
| 66 |
|
| 67 |
document.querySelectorAll('.settings-nav-item').forEach(btn => { |
| 68 |
btn.classList.toggle('active', btn.dataset.section === section); |
| 69 |
}); |
| 70 |
|
| 71 |
const container = document.getElementById('settings-content'); |
| 72 |
if (!container) return; |
| 73 |
|
| 74 |
switch (section) { |
| 75 |
case 'appearance': await renderAppearance(container); break; |
| 76 |
case 'notifications': renderNotifications(container); break; |
| 77 |
case 'email': await GoingsOn.emails.renderAccountsSection(container); break; |
| 78 |
case 'planning': renderPlanning(container); break; |
| 79 |
case 'plugins': await renderPlugins(container); break; |
| 80 |
case 'sync': await renderSync(container); break; |
| 81 |
case 'data': await renderData(container); break; |
| 82 |
case 'about': await renderAbout(container); break; |
| 83 |
} |
| 84 |
} |
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
async function renderAppearance(container) { |
| 89 |
const savedTheme = localStorage.getItem('goingson-theme') || 'system'; |
| 90 |
const { light: lightThemes, dark: darkThemes, highContrast: highContrastThemes } = await GoingsOn.themes.getByType(); |
| 91 |
|
| 92 |
const highContrastGroup = highContrastThemes.length > 0 |
| 93 |
? `<optgroup label="High Contrast"> |
| 94 |
${highContrastThemes.map(t => `<option value="${t.id}" ${savedTheme === t.id ? 'selected' : ''}>${esc(t.name)}</option>`).join('')} |
| 95 |
</optgroup>` |
| 96 |
: ''; |
| 97 |
|
| 98 |
container.innerHTML = ` |
| 99 |
<div class="settings-section"> |
| 100 |
<h3 class="settings-heading">Appearance</h3> |
| 101 |
<div class="form-group"> |
| 102 |
<label class="form-label">Theme</label> |
| 103 |
<select id="theme-selector" class="form-select" onchange="GoingsOn.themes.onChange(this.value)"> |
| 104 |
<optgroup label="System"> |
| 105 |
<option value="system" ${savedTheme === 'system' ? 'selected' : ''}>Follow System</option> |
| 106 |
</optgroup> |
| 107 |
<optgroup label="Light Themes"> |
| 108 |
${lightThemes.map(t => `<option value="${t.id}" ${savedTheme === t.id ? 'selected' : ''}>${esc(t.name)}</option>`).join('')} |
| 109 |
</optgroup> |
| 110 |
<optgroup label="Dark Themes"> |
| 111 |
${darkThemes.map(t => `<option value="${t.id}" ${savedTheme === t.id ? 'selected' : ''}>${esc(t.name)}</option>`).join('')} |
| 112 |
</optgroup> |
| 113 |
${highContrastGroup} |
| 114 |
</select> |
| 115 |
<p class="form-hint" style="margin-top: 0.5rem;">Choose a color theme for the interface.</p> |
| 116 |
<div class="row-flex row-flex-2" style="margin-top: 0.5rem;"> |
| 117 |
<button class="btn btn-small" onclick="GoingsOn.themes.importTheme()">Import Theme</button> |
| 118 |
<button class="btn btn-small" onclick="GoingsOn.themes.exportTheme()">Export Current</button> |
| 119 |
</div> |
| 120 |
</div> |
| 121 |
</div> |
| 122 |
`; |
| 123 |
} |
| 124 |
|
| 125 |
function renderNotifications(container) { |
| 126 |
container.innerHTML = ` |
| 127 |
<div class="settings-section"> |
| 128 |
<h3 class="settings-heading">Notifications</h3> |
| 129 |
<div class="form-group"> |
| 130 |
<label class="form-label">Event indicator lead time</label> |
| 131 |
<select id="event-lead-time-selector" class="form-select" onchange="GoingsOn.settings.onEventLeadTimeChange(this.value)"> |
| 132 |
${[5, 10, 15, 30, 60].map(m => { |
| 133 |
const currentLead = parseInt(localStorage.getItem('goingson-event-lead-minutes') || '15', 10); |
| 134 |
const label = m === 60 ? '1 hour' : m + ' minutes'; |
| 135 |
const suffix = m === 15 ? ' (default)' : ''; |
| 136 |
return '<option value="' + m + '"' + (currentLead === m ? ' selected' : '') + '>' + label + suffix + '</option>'; |
| 137 |
}).join('')} |
| 138 |
</select> |
| 139 |
<p class="form-hint" style="margin-top: 0.5rem;">How far in advance the Events tab dot turns yellow.</p> |
| 140 |
</div> |
| 141 |
</div> |
| 142 |
`; |
| 143 |
} |
| 144 |
|
| 145 |
function renderPlanning(container) { |
| 146 |
container.innerHTML = ` |
| 147 |
<div class="settings-section"> |
| 148 |
<h3 class="settings-heading">Planning & Review</h3> |
| 149 |
<div class="form-group"> |
| 150 |
<label class="form-label">Work hours</label> |
| 151 |
<div class="work-hours-row"> |
| 152 |
<select class="form-select" onchange="GoingsOn.settings.onWorkHoursChange('start', this.value)"> |
| 153 |
${buildHourOptions(parseInt(localStorage.getItem('goingson-work-start-hour') || '9', 10))} |
| 154 |
</select> |
| 155 |
<span>to</span> |
| 156 |
<select class="form-select" onchange="GoingsOn.settings.onWorkHoursChange('end', this.value)"> |
| 157 |
${buildHourOptions(parseInt(localStorage.getItem('goingson-work-end-hour') || '17', 10))} |
| 158 |
</select> |
| 159 |
</div> |
| 160 |
<p class="form-hint" style="margin-top: 0.5rem;">Controls when plan/review nudge dots appear.</p> |
| 161 |
</div> |
| 162 |
<div class="form-group"> |
| 163 |
<label class="form-label">Plan nudges</label> |
| 164 |
<select class="form-select" onchange="localStorage.setItem('goingson-plan-nudges', this.value)"> |
| 165 |
<option value="enabled" ${localStorage.getItem('goingson-plan-nudges') !== 'disabled' ? 'selected' : ''}>Enabled (default)</option> |
| 166 |
<option value="disabled" ${localStorage.getItem('goingson-plan-nudges') === 'disabled' ? 'selected' : ''}>Disabled</option> |
| 167 |
</select> |
| 168 |
</div> |
| 169 |
<div class="form-group"> |
| 170 |
<label class="form-label">Review nudges</label> |
| 171 |
<select class="form-select" onchange="localStorage.setItem('goingson-review-nudges', this.value)"> |
| 172 |
<option value="enabled" ${localStorage.getItem('goingson-review-nudges') !== 'disabled' ? 'selected' : ''}>Enabled (default)</option> |
| 173 |
<option value="disabled" ${localStorage.getItem('goingson-review-nudges') === 'disabled' ? 'selected' : ''}>Disabled</option> |
| 174 |
</select> |
| 175 |
</div> |
| 176 |
</div> |
| 177 |
`; |
| 178 |
} |
| 179 |
|
| 180 |
async function renderPlugins(container) { |
| 181 |
let allPlugins = []; |
| 182 |
let enabledPlugins = []; |
| 183 |
|
| 184 |
try { |
| 185 |
[allPlugins, enabledPlugins] = await Promise.all([ |
| 186 |
GoingsOn.api.plugins.listImport(), |
| 187 |
GoingsOn.api.plugins.listEnabled(), |
| 188 |
]); |
| 189 |
} catch (err) { |
| 190 |
container.innerHTML = `<p class="text-danger">Failed to load plugins: ${esc(GoingsOn.utils.getErrorMessage(err))}</p>`; |
| 191 |
return; |
| 192 |
} |
| 193 |
|
| 194 |
const enabledIds = new Set(enabledPlugins.map(p => p.id)); |
| 195 |
const pluginsList = allPlugins.length === 0 |
| 196 |
? '<p class="empty-italic">No plugins installed. Drop a plugin file into the GoingsOn plugins directory inside your OS app-data location.</p>' |
| 197 |
: allPlugins.map(plugin => renderPluginItem(plugin, enabledIds.has(plugin.id))).join(''); |
| 198 |
|
| 199 |
container.innerHTML = ` |
| 200 |
<div class="settings-section"> |
| 201 |
<h3 class="settings-heading">Plugins</h3> |
| 202 |
<p class="settings-desc"> |
| 203 |
Plugins extend GoingsOn with support for importing data from various formats. |
| 204 |
</p> |
| 205 |
<div class="plugin-list" id="plugin-list"> |
| 206 |
${pluginsList} |
| 207 |
</div> |
| 208 |
</div> |
| 209 |
`; |
| 210 |
} |
| 211 |
|
| 212 |
async function renderSync(container) { |
| 213 |
|
| 214 |
if (GoingsOn.settings.renderSyncSection) { |
| 215 |
await GoingsOn.settings.renderSyncSection(container); |
| 216 |
} else { |
| 217 |
container.innerHTML = '<p class="text-secondary">Cloud sync module not loaded.</p>'; |
| 218 |
} |
| 219 |
} |
| 220 |
|
| 221 |
async function renderData(container) { |
| 222 |
|
| 223 |
let backupHtml = ''; |
| 224 |
try { |
| 225 |
const settings = await GoingsOn.api.export.getBackupSettings(); |
| 226 |
const lastBackupText = settings.lastBackupAt |
| 227 |
? `Last backup: ${new Date(settings.lastBackupAt).toLocaleString()}` |
| 228 |
: 'No backups yet'; |
| 229 |
|
| 230 |
const frequencyOptions = [ |
| 231 |
{ value: 15, label: 'Every 15 minutes (Recommended)' }, |
| 232 |
{ value: 30, label: 'Every 30 minutes' }, |
| 233 |
{ value: 60, label: 'Every hour' }, |
| 234 |
{ value: 360, label: 'Every 6 hours' }, |
| 235 |
{ value: 1440, label: 'Daily' }, |
| 236 |
]; |
| 237 |
|
| 238 |
const retentionOptions = [ |
| 239 |
{ value: 1, label: 'Keep 1 backup (Recommended)' }, |
| 240 |
{ value: 3, label: 'Keep 3 backups' }, |
| 241 |
{ value: 7, label: 'Keep 7 backups' }, |
| 242 |
{ value: 14, label: 'Keep 14 backups' }, |
| 243 |
{ value: 0, label: 'Keep all backups' }, |
| 244 |
]; |
| 245 |
|
| 246 |
const renderFormField = GoingsOn.ui.renderFormField; |
| 247 |
backupHtml = ` |
| 248 |
<div class="settings-section-block"> |
| 249 |
<h4 class="settings-subheading">Backups</h4> |
| 250 |
<div class="settings-actions-row"> |
| 251 |
<button class="btn btn-secondary" onclick="GoingsOn.export.createBackup()">Create Backup</button> |
| 252 |
<button class="btn btn-secondary" onclick="GoingsOn.export.openBackupsModal()">Manage Backups</button> |
| 253 |
</div> |
| 254 |
|
| 255 |
<h4 class="settings-subheading">Automatic Backups</h4> |
| 256 |
<p class="settings-desc-block"> |
| 257 |
Automatic backups protect your data by creating compressed snapshots on a schedule. |
| 258 |
</p> |
| 259 |
|
| 260 |
<div class="form-group"> |
| 261 |
<label class="form-checkbox-label"> |
| 262 |
<input type="checkbox" id="backup-enabled" ${settings.autoBackupEnabled ? 'checked' : ''}> |
| 263 |
<span>Enable automatic backups</span> |
| 264 |
</label> |
| 265 |
</div> |
| 266 |
${renderFormField({ |
| 267 |
kind: 'select', |
| 268 |
name: 'backup-frequency', |
| 269 |
id: 'backup-frequency', |
| 270 |
label: 'Backup Frequency', |
| 271 |
value: settings.backupFrequencyMinutes, |
| 272 |
options: frequencyOptions.map(o => ({ |
| 273 |
value: String(o.value), |
| 274 |
label: o.label, |
| 275 |
selected: settings.backupFrequencyMinutes === o.value, |
| 276 |
})), |
| 277 |
})} |
| 278 |
${renderFormField({ |
| 279 |
kind: 'select', |
| 280 |
name: 'backup-retention', |
| 281 |
id: 'backup-retention', |
| 282 |
label: 'Retention Policy', |
| 283 |
value: settings.maxBackupsToKeep, |
| 284 |
options: retentionOptions.map(o => ({ |
| 285 |
value: String(o.value), |
| 286 |
label: o.label, |
| 287 |
selected: settings.maxBackupsToKeep === o.value, |
| 288 |
})), |
| 289 |
hint: 'Older backups are automatically deleted to save space.', |
| 290 |
})} |
| 291 |
<div class="settings-actions-row settings-actions-row--center"> |
| 292 |
<button class="btn btn-primary" onclick="GoingsOn.export.saveBackupSettings()">Save Backup Settings</button> |
| 293 |
<span class="settings-status-text">${esc(lastBackupText)}</span> |
| 294 |
</div> |
| 295 |
</div> |
| 296 |
`; |
| 297 |
} catch (err) { |
| 298 |
backupHtml = ` |
| 299 |
<div class="settings-section-block"> |
| 300 |
<h4 class="settings-subheading">Backups</h4> |
| 301 |
<div class="settings-actions-row"> |
| 302 |
<button class="btn btn-secondary" onclick="GoingsOn.export.createBackup()">Create Backup</button> |
| 303 |
<button class="btn btn-secondary" onclick="GoingsOn.export.openBackupsModal()">Manage Backups</button> |
| 304 |
</div> |
| 305 |
</div> |
| 306 |
`; |
| 307 |
} |
| 308 |
|
| 309 |
container.innerHTML = ` |
| 310 |
<div class="settings-section"> |
| 311 |
<h3 class="settings-heading">Import & Export</h3> |
| 312 |
<p class="settings-desc"> |
| 313 |
Import data from external sources or export for safekeeping. |
| 314 |
</p> |
| 315 |
|
| 316 |
<div class="settings-actions-row"> |
| 317 |
<button class="btn btn-secondary" onclick="GoingsOn.importExternal.openDialog();">Import Contacts / Calendar</button> |
| 318 |
<button class="btn btn-secondary" onclick="GoingsOn.import.openModal();">Import via Plugin</button> |
| 319 |
</div> |
| 320 |
|
| 321 |
<div class="settings-actions-row"> |
| 322 |
<button class="btn btn-secondary" onclick="GoingsOn.export.exportJSON()">Export All (JSON)</button> |
| 323 |
<button class="btn btn-secondary" onclick="GoingsOn.export.exportTasksCSV()">Export Tasks (CSV)</button> |
| 324 |
<button class="btn btn-secondary" onclick="GoingsOn.export.exportEventsICS()">Export Calendar (ICS)</button> |
| 325 |
</div> |
| 326 |
|
| 327 |
${backupHtml} |
| 328 |
</div> |
| 329 |
`; |
| 330 |
} |
| 331 |
|
| 332 |
|
| 333 |
|
| 334 |
|
| 335 |
* Phase 7 Tier 2 #7 — surface the version + support info in Settings rather |
| 336 |
* than only in the one-shot welcome modal. Required for support flows. |
| 337 |
|
| 338 |
async function setUpdateCheckOnLaunch(enabled) { |
| 339 |
try { |
| 340 |
await GoingsOn.api.preferences.setUpdateCheckOnLaunch(enabled); |
| 341 |
GoingsOn.ui.showToast(enabled ? 'Update checks enabled' : 'Update checks disabled'); |
| 342 |
} catch (err) { |
| 343 |
GoingsOn.ui.showToast('Failed to save preference: ' + GoingsOn.utils.getErrorMessage(err), 'error'); |
| 344 |
} |
| 345 |
} |
| 346 |
|
| 347 |
async function renderAbout(container) { |
| 348 |
let appVersion = 'unknown'; |
| 349 |
try { appVersion = await window.__TAURI__.app.getVersion(); } catch (_) {} |
| 350 |
|
| 351 |
const platform = (navigator.userAgentData?.platform || navigator.platform || 'unknown'); |
| 352 |
|
| 353 |
let updateCheckOnLaunch = true; |
| 354 |
try { |
| 355 |
const prefs = await GoingsOn.api.preferences.get(); |
| 356 |
updateCheckOnLaunch = prefs.updateCheckOnLaunch !== false; |
| 357 |
} catch (_) {} |
| 358 |
|
| 359 |
container.innerHTML = ` |
| 360 |
<div class="settings-section"> |
| 361 |
<h3 class="settings-heading">About GoingsOn</h3> |
| 362 |
<p class="settings-desc">Tasks, email, calendar, contacts.</p> |
| 363 |
|
| 364 |
<dl class="about-info-list"> |
| 365 |
<dt>Version</dt><dd id="about-app-version">${esc(appVersion)}</dd> |
| 366 |
<dt>Platform</dt><dd>${esc(platform)}</dd> |
| 367 |
<dt>Publisher</dt><dd>Make Creative, LLC</dd> |
| 368 |
<dt>License</dt><dd>PolyForm Noncommercial 1.0.0</dd> |
| 369 |
<dt>Contact</dt><dd><a href="mailto:info@makenot.work">info@makenot.work</a></dd> |
| 370 |
<dt>Source</dt><dd><a href="https://makenot.work" target="_blank" rel="noopener">makenot.work</a></dd> |
| 371 |
<dt>Privacy</dt><dd><a href="https://makenot.work/policy" target="_blank" rel="noopener">makenot.work/policy</a></dd> |
| 372 |
</dl> |
| 373 |
|
| 374 |
<div class="settings-toggle-row"> |
| 375 |
<div> |
| 376 |
<p class="settings-subheading">Check for updates on launch</p> |
| 377 |
<p class="settings-desc">When a new signed release is available, a banner appears in the app. Install is always user-initiated.</p> |
| 378 |
</div> |
| 379 |
<label class="toggle-switch"> |
| 380 |
<input type="checkbox" ${updateCheckOnLaunch ? 'checked' : ''} |
| 381 |
onchange="GoingsOn.settings.setUpdateCheckOnLaunch(this.checked)"> |
| 382 |
<span class="toggle-slider"></span> |
| 383 |
</label> |
| 384 |
</div> |
| 385 |
|
| 386 |
<p class="settings-desc-block about-copyright">© 2026 Make Creative, LLC</p> |
| 387 |
</div> |
| 388 |
`; |
| 389 |
} |
| 390 |
|
| 391 |
|
| 392 |
|
| 393 |
function renderPluginItem(plugin, isEnabled) { |
| 394 |
const config = plugin.plugin_type?.import || {}; |
| 395 |
const extensions = config.file_extensions || []; |
| 396 |
const entityTypes = config.entity_types || []; |
| 397 |
|
| 398 |
return ` |
| 399 |
<div class="plugin-item" data-plugin-id="${escAttr(plugin.id)}"> |
| 400 |
<div class="plugin-info"> |
| 401 |
<span class="plugin-name">${esc(plugin.name)}</span> |
| 402 |
<span class="plugin-version">v${esc(plugin.version)}</span> |
| 403 |
<p class="plugin-description">${esc(plugin.description)}</p> |
| 404 |
<span class="plugin-extensions"> |
| 405 |
Files: .${extensions.join(', .')} | Types: ${entityTypes.join(', ')} |
| 406 |
</span> |
| 407 |
</div> |
| 408 |
<div class="plugin-actions"> |
| 409 |
<label class="toggle-switch"> |
| 410 |
<input type="checkbox" ${isEnabled ? 'checked' : ''} |
| 411 |
onchange="GoingsOn.settings.togglePlugin('${escAttr(plugin.id)}', this.checked)"> |
| 412 |
<span class="toggle-slider"></span> |
| 413 |
</label> |
| 414 |
</div> |
| 415 |
</div> |
| 416 |
`; |
| 417 |
} |
| 418 |
|
| 419 |
|
| 420 |
|
| 421 |
async function togglePlugin(pluginId, enabled) { |
| 422 |
try { |
| 423 |
if (enabled) { |
| 424 |
await GoingsOn.api.plugins.enable(pluginId); |
| 425 |
GoingsOn.ui.showToast('Plugin enabled'); |
| 426 |
} else { |
| 427 |
await GoingsOn.api.plugins.disable(pluginId); |
| 428 |
GoingsOn.ui.showToast('Plugin disabled'); |
| 429 |
} |
| 430 |
} catch (err) { |
| 431 |
GoingsOn.ui.showToast('Failed to update plugin: ' + GoingsOn.utils.getErrorMessage(err), 'error'); |
| 432 |
|
| 433 |
const container = document.getElementById('settings-content'); |
| 434 |
if (container) await renderPlugins(container); |
| 435 |
} |
| 436 |
} |
| 437 |
|
| 438 |
|
| 439 |
|
| 440 |
function onEventLeadTimeChange(value) { |
| 441 |
localStorage.setItem('goingson-event-lead-minutes', value); |
| 442 |
if (GoingsOn.events && GoingsOn.events.updateEventStatusDot) { |
| 443 |
GoingsOn.events.updateEventStatusDot(); |
| 444 |
} |
| 445 |
} |
| 446 |
|
| 447 |
function buildHourOptions(selected) { |
| 448 |
let html = ''; |
| 449 |
for (let h = 0; h < 24; h++) { |
| 450 |
const label = h === 0 ? '12:00 AM' : h < 12 ? `${h}:00 AM` : h === 12 ? '12:00 PM' : `${h - 12}:00 PM`; |
| 451 |
html += `<option value="${h}" ${h === selected ? 'selected' : ''}>${label}</option>`; |
| 452 |
} |
| 453 |
return html; |
| 454 |
} |
| 455 |
|
| 456 |
function onWorkHoursChange(which, value) { |
| 457 |
const key = which === 'start' ? 'goingson-work-start-hour' : 'goingson-work-end-hour'; |
| 458 |
localStorage.setItem(key, value); |
| 459 |
} |
| 460 |
|
| 461 |
|
| 462 |
|
| 463 |
GoingsOn.settings = { |
| 464 |
open: openSettings, |
| 465 |
load: loadSettings, |
| 466 |
showSection, |
| 467 |
goBack, |
| 468 |
openPluginsModal: () => showSection('plugins'), |
| 469 |
togglePlugin, |
| 470 |
onEventLeadTimeChange, |
| 471 |
onWorkHoursChange, |
| 472 |
setUpdateCheckOnLaunch, |
| 473 |
}; |
| 474 |
|
| 475 |
})(); |
| 476 |
|