WIP Feedback
This commit is contained in:
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
#
|
||||
# Usage: ./run-screenshots.sh
|
||||
#
|
||||
# Output: screenshots/*.png (gitignored)
|
||||
# Output: screenshots/{mobile,desktop}/{manual-nickelmenu,manual-patches,connected-nickelmenu,connected-patches,edge-cases}/*.png (gitignored)
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
* Run: ./run-screenshots.sh
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { injectMockDevice } from './helpers/mock-device.js';
|
||||
import { injectMockDevice, overrideFirmwareURLs } from './helpers/mock-device.js';
|
||||
import { hasFirmwareZip } from './helpers/assets.js';
|
||||
|
||||
const shot = async (page, name, testInfo) => {
|
||||
const shot = async (page, folder, name, testInfo) => {
|
||||
const project = testInfo.project.name;
|
||||
await page.waitForTimeout(200);
|
||||
await page.screenshot({ path: `screenshots/${project}/${name}.png`, fullPage: true });
|
||||
await page.screenshot({ path: `screenshots/${project}/${folder}/${name}.png`, fullPage: true });
|
||||
};
|
||||
|
||||
/** Dismiss the mobile warning modal if it's open. */
|
||||
@@ -23,143 +24,280 @@ const dismissMobileModal = async (page) => {
|
||||
}
|
||||
};
|
||||
|
||||
test('capture all steps', async ({ page }, testInfo) => {
|
||||
// ============================================================
|
||||
// 1. Manual NickelMenu flow
|
||||
// ============================================================
|
||||
|
||||
test('manual nickelmenu', async ({ page }, testInfo) => {
|
||||
const dir = 'manual-nickelmenu';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
// 1. Connect step (with mobile modal if applicable)
|
||||
await page.goto('/');
|
||||
if (isMobile) {
|
||||
// Capture the mobile warning modal
|
||||
await expect(page.locator('#mobile-dialog')).toBeVisible();
|
||||
await page.screenshot({ path: `screenshots/mobile/00-mobile-warning.png` });
|
||||
await page.click('#btn-mobile-continue');
|
||||
await expect(page.locator('#mobile-dialog')).not.toBeVisible();
|
||||
}
|
||||
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||
await injectMockDevice(page);
|
||||
await shot(page, '01-connect', testInfo);
|
||||
|
||||
// 2. Connection instructions
|
||||
await page.click('#btn-connect');
|
||||
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||
await shot(page, '02-connect-instructions', testInfo);
|
||||
|
||||
// 2b. Connection instructions with disclaimer open
|
||||
await page.click('details.banner--accent summary');
|
||||
await page.waitForTimeout(100);
|
||||
await shot(page, '03-connect-instructions-disclaimer', testInfo);
|
||||
|
||||
// 3. Device detected
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, '04-device', testInfo);
|
||||
|
||||
// 4. Mode selection
|
||||
await page.click('#btn-device-next');
|
||||
// Click "Build downloadable archive" to enter manual mode
|
||||
await page.click('#btn-manual');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, '05-mode-selection', testInfo);
|
||||
await shot(page, dir, '01-mode-selection', testInfo);
|
||||
|
||||
// 5a. NickelMenu config
|
||||
// Select NickelMenu → config
|
||||
await page.click('input[name="mode"][value="nickelmenu"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
|
||||
await shot(page, '06-nickelmenu-config', testInfo);
|
||||
await shot(page, dir, '02-nickelmenu-config', testInfo);
|
||||
|
||||
// 5b. NickelMenu features (preset)
|
||||
// Preset → features
|
||||
await page.click('input[value="preset"]');
|
||||
await page.click('#btn-nm-next');
|
||||
await expect(page.locator('#step-nm-features')).not.toBeHidden();
|
||||
await shot(page, '07-nickelmenu-features', testInfo);
|
||||
await shot(page, dir, '03-nickelmenu-features', testInfo);
|
||||
|
||||
// 5c. NickelMenu review
|
||||
// Features → review (only download button in manual mode)
|
||||
await page.click('#btn-nm-features-next');
|
||||
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||
await shot(page, '08-nickelmenu-review', testInfo);
|
||||
await shot(page, dir, '04-nickelmenu-review', testInfo);
|
||||
|
||||
// Go back to mode and try patches path
|
||||
await page.click('#btn-nm-review-back');
|
||||
await page.click('#btn-nm-features-back');
|
||||
await page.click('#btn-nm-back');
|
||||
// Download → done
|
||||
await page.click('#btn-nm-download');
|
||||
const nmDone = page.locator('#step-nm-done');
|
||||
await expect(nmDone).not.toBeHidden();
|
||||
await shot(page, dir, '05-nickelmenu-done', testInfo);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 2. Manual Patches flow
|
||||
// ============================================================
|
||||
|
||||
test('manual patches', async ({ page }, testInfo) => {
|
||||
test.skip(!hasFirmwareZip(), 'Firmware zip not available');
|
||||
|
||||
const dir = 'manual-patches';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
await page.goto('/');
|
||||
await injectMockDevice(page);
|
||||
await page.waitForFunction(() => !!window.FIRMWARE_DOWNLOADS);
|
||||
await overrideFirmwareURLs(page);
|
||||
|
||||
if (isMobile) {
|
||||
await page.click('#btn-mobile-continue');
|
||||
await expect(page.locator('#mobile-dialog')).not.toBeVisible();
|
||||
}
|
||||
|
||||
// Click "Build downloadable archive" to enter manual mode
|
||||
await page.click('#btn-manual');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, dir, '01-mode-selection', testInfo);
|
||||
|
||||
// Select Patches → version selection
|
||||
await page.click('input[name="mode"][value="patches"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||
await shot(page, '09-patches-config', testInfo);
|
||||
await expect(page.locator('#step-manual-version')).not.toBeHidden();
|
||||
await shot(page, dir, '02-version-selection', testInfo);
|
||||
|
||||
// 6b. Expand a patch section and select a patch
|
||||
// Select firmware version and model
|
||||
await page.selectOption('#manual-version', { index: 1 });
|
||||
await expect(page.locator('#manual-model')).not.toBeHidden();
|
||||
await page.selectOption('#manual-model', { index: 1 });
|
||||
await page.click('#btn-manual-confirm');
|
||||
|
||||
// Patches config
|
||||
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||
await shot(page, dir, '03-patches-config', testInfo);
|
||||
|
||||
// Expand section and select a patch
|
||||
const section = page.locator('.patch-file-section').first();
|
||||
await section.locator('summary').click();
|
||||
const patchLabel = section.locator('label').filter({ has: page.locator('.patch-name:not(.patch-name-none)') }).first();
|
||||
await patchLabel.locator('input').check();
|
||||
await shot(page, '10-patches-selected', testInfo);
|
||||
await shot(page, dir, '04-patches-selected', testInfo);
|
||||
|
||||
// Review & build
|
||||
await page.click('#btn-patches-next');
|
||||
await expect(page.locator('#step-firmware')).not.toBeHidden();
|
||||
await shot(page, dir, '05-build', testInfo);
|
||||
|
||||
// Build
|
||||
await page.click('#btn-build');
|
||||
const stepDone = page.locator('#step-done');
|
||||
await expect(stepDone).not.toBeHidden({ timeout: 60_000 });
|
||||
await shot(page, dir, '06-patches-done', testInfo);
|
||||
|
||||
// Download
|
||||
await page.click('#btn-download');
|
||||
await expect(stepDone.locator('#download-instructions')).toBeVisible();
|
||||
await shot(page, dir, '07-patches-done-download', testInfo);
|
||||
});
|
||||
|
||||
test('nickelmenu done with feedback', async ({ page }, testInfo) => {
|
||||
// Enable analytics so the feedback widget appears
|
||||
await page.addInitScript(() => { window.__ANALYTICS_ENABLED = true; });
|
||||
// ============================================================
|
||||
// 3. Connected NickelMenu flow
|
||||
// ============================================================
|
||||
|
||||
test('connected nickelmenu', async ({ page }, testInfo) => {
|
||||
const dir = 'connected-nickelmenu';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
if (isMobile) {
|
||||
await expect(page.locator('#mobile-dialog')).toBeVisible();
|
||||
await page.screenshot({ path: `screenshots/mobile/${dir}/00-mobile-warning.png` });
|
||||
await page.click('#btn-mobile-continue');
|
||||
}
|
||||
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||
await injectMockDevice(page);
|
||||
await shot(page, dir, '01-connect', testInfo);
|
||||
|
||||
// Connect device → mode selection → NickelMenu
|
||||
// Connection instructions
|
||||
await page.click('#btn-connect');
|
||||
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||
await shot(page, dir, '02-connect-instructions', testInfo);
|
||||
|
||||
// Device detected
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, dir, '03-device', testInfo);
|
||||
|
||||
// Mode selection
|
||||
await page.click('#btn-device-next');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, dir, '04-mode-selection', testInfo);
|
||||
|
||||
// NickelMenu config
|
||||
await page.click('input[name="mode"][value="nickelmenu"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
|
||||
await shot(page, dir, '05-nickelmenu-config', testInfo);
|
||||
|
||||
// NickelMenu-only → review → write to device
|
||||
await page.click('input[value="nickelmenu-only"]');
|
||||
// Preset → features
|
||||
await page.click('input[value="preset"]');
|
||||
await page.click('#btn-nm-next');
|
||||
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||
await page.click('#btn-nm-write');
|
||||
await expect(page.locator('#step-nm-features')).not.toBeHidden();
|
||||
await shot(page, dir, '06-nickelmenu-features', testInfo);
|
||||
|
||||
// Wait for done step
|
||||
// Features → review
|
||||
await page.click('#btn-nm-features-next');
|
||||
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||
await shot(page, dir, '07-nickelmenu-review', testInfo);
|
||||
|
||||
// Write to device → done
|
||||
await page.click('#btn-nm-write');
|
||||
const nmDone = page.locator('#step-nm-done');
|
||||
await expect(nmDone).not.toBeHidden();
|
||||
await expect(nmDone.locator('.feedback')).toBeVisible();
|
||||
await shot(page, '15-done-feedback', testInfo);
|
||||
await shot(page, dir, '08-nickelmenu-done', testInfo);
|
||||
});
|
||||
|
||||
// Click thumbs up and capture the thank-you state
|
||||
await nmDone.locator('.feedback-btn[data-vote="up"]').click();
|
||||
await expect(nmDone.locator('.feedback-thanks')).toBeVisible();
|
||||
await shot(page, '16-done-feedback-voted', testInfo);
|
||||
// ============================================================
|
||||
// 4. Connected Patches flow
|
||||
// ============================================================
|
||||
|
||||
test('connected patches', async ({ page }, testInfo) => {
|
||||
test.skip(!hasFirmwareZip(), 'Firmware zip not available');
|
||||
|
||||
const dir = 'connected-patches';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
await page.goto('/');
|
||||
await injectMockDevice(page);
|
||||
await page.waitForFunction(() => !!window.FIRMWARE_DOWNLOADS);
|
||||
await overrideFirmwareURLs(page);
|
||||
|
||||
if (isMobile) {
|
||||
await page.click('#btn-mobile-continue');
|
||||
await expect(page.locator('#mobile-dialog')).not.toBeVisible();
|
||||
}
|
||||
|
||||
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||
await shot(page, dir, '01-connect', testInfo);
|
||||
|
||||
// Connection instructions
|
||||
await page.click('#btn-connect');
|
||||
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||
await shot(page, dir, '02-connect-instructions', testInfo);
|
||||
|
||||
// Device detected
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, dir, '03-device', testInfo);
|
||||
|
||||
// Mode selection
|
||||
await page.click('#btn-device-next');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, dir, '04-mode-selection', testInfo);
|
||||
|
||||
// Patches config
|
||||
await page.click('input[name="mode"][value="patches"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||
await shot(page, dir, '05-patches-config', testInfo);
|
||||
|
||||
// Expand section and select a patch
|
||||
const section = page.locator('.patch-file-section').first();
|
||||
await section.locator('summary').click();
|
||||
const patchLabel = section.locator('label').filter({ has: page.locator('.patch-name:not(.patch-name-none)') }).first();
|
||||
await patchLabel.locator('input').check();
|
||||
await shot(page, dir, '06-patches-selected', testInfo);
|
||||
|
||||
// Review & build
|
||||
await page.click('#btn-patches-next');
|
||||
await expect(page.locator('#step-firmware')).not.toBeHidden();
|
||||
await shot(page, dir, '07-build', testInfo);
|
||||
|
||||
// Build → done
|
||||
await page.click('#btn-build');
|
||||
const stepDone = page.locator('#step-done');
|
||||
await expect(stepDone).not.toBeHidden({ timeout: 60_000 });
|
||||
await shot(page, dir, '08-patches-done', testInfo);
|
||||
|
||||
// Download
|
||||
await page.click('#btn-download');
|
||||
await expect(stepDone.locator('#download-instructions')).toBeVisible();
|
||||
await shot(page, dir, '09-patches-done-download', testInfo);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 5. Edge cases
|
||||
// ============================================================
|
||||
|
||||
test('unsupported browser', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.addInitScript(() => { delete window.showDirectoryPicker; });
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await expect(page.locator('#connect-unsupported-hint')).toBeVisible();
|
||||
await shot(page, dir, 'unsupported-browser', testInfo);
|
||||
});
|
||||
|
||||
test('incompatible firmware', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await injectMockDevice(page, { firmware: '5.0.0' });
|
||||
await page.click('#btn-connect');
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, '11-device-incompatible', testInfo);
|
||||
await shot(page, dir, 'incompatible-firmware', testInfo);
|
||||
});
|
||||
|
||||
test('unknown model', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await injectMockDevice(page, { serial: 'X9990A0000000' });
|
||||
await page.click('#btn-connect');
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, '12-device-unknown', testInfo);
|
||||
});
|
||||
|
||||
test('unsupported browser', async ({ page }, testInfo) => {
|
||||
await page.addInitScript(() => { delete window.showDirectoryPicker; });
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await expect(page.locator('#connect-unsupported-hint')).toBeVisible();
|
||||
await shot(page, '13-connect-unsupported', testInfo);
|
||||
await shot(page, dir, 'unknown-model', testInfo);
|
||||
});
|
||||
|
||||
test('disclaimer dialog', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await page.click('#btn-how-it-works');
|
||||
await expect(page.locator('#how-it-works-dialog')).toBeVisible();
|
||||
await page.waitForTimeout(200);
|
||||
await page.screenshot({ path: `screenshots/${testInfo.project.name}/14-disclaimer-dialog.png` });
|
||||
await page.screenshot({ path: `screenshots/${testInfo.project.name}/${dir}/disclaimer-dialog.png` });
|
||||
});
|
||||
|
||||
41
web/src/css/components/feedback.css
Normal file
41
web/src/css/components/feedback.css
Normal file
@@ -0,0 +1,41 @@
|
||||
.feedback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.feedback-text {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.feedback-buttons {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.feedback-btn {
|
||||
font-size: 1rem;
|
||||
padding: 0.2rem 0.55rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--info-border);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
color: inherit;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.feedback-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.feedback-btn--selected {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-color: var(--info-text);
|
||||
}
|
||||
|
||||
.feedback-thanks {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -69,7 +69,7 @@ select + .fallback-hint {
|
||||
}
|
||||
|
||||
.restart-hint {
|
||||
margin-top: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@import './components/info-card.css';
|
||||
@import './components/step-nav.css';
|
||||
@import './components/modal.css';
|
||||
@import './components/feedback.css';
|
||||
@import './layout/hero.css';
|
||||
@import './layout/steps.css';
|
||||
@import './layout/footer.css';
|
||||
|
||||
@@ -305,7 +305,15 @@
|
||||
<strong>Safely eject your Kobo and let it reboot.</strong> Please be patient, NickelMenu will be automatically removed during the reboot.
|
||||
A "glitchy" horizontal line may briefly appear on screen after restarting — this is normal, as NickelMenu removes itself.
|
||||
</p>
|
||||
<p class="restart-hint">You can always restart the entire flow by reloading the page.</p>
|
||||
<p class="restart-hint">You can always restart the entire flow by reloading the page, if you want to try again for another configuration or undo the changes that were made.</p>
|
||||
<div class="banner banner--info feedback" hidden>
|
||||
<span class="feedback-text">Did you find the wizard easy to use?</span>
|
||||
<span class="feedback-thanks" hidden>Thank you for your feedback!</span>
|
||||
<span class="feedback-buttons">
|
||||
<button class="feedback-btn" data-vote="up" type="button">👍</button>
|
||||
<button class="feedback-btn" data-vote="down" type="button">👎</button>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Step 2 (patches path): Configure patches -->
|
||||
@@ -378,7 +386,15 @@
|
||||
<li>The device will reboot and apply the patches automatically.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p class="restart-hint">You can always restart the entire flow by reloading the page.</p>
|
||||
<p class="restart-hint">You can always restart the entire flow by reloading the page, if you want to try again for another configuration or undo the changes that were made.</p>
|
||||
<div class="banner banner--info feedback" hidden>
|
||||
<span class="feedback-text">Did you find the wizard easy to use?</span>
|
||||
<span class="feedback-thanks" hidden>Thank you for your feedback!</span>
|
||||
<span class="feedback-buttons">
|
||||
<button class="feedback-btn" data-vote="up" type="button">👍</button>
|
||||
<button class="feedback-btn" data-vote="down" type="button">👎</button>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Error state -->
|
||||
|
||||
@@ -97,6 +97,33 @@ export async function fetchOrThrow(url, errorPrefix = 'Fetch failed') {
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire up a .feedback banner inside a container element.
|
||||
* Shows text + vote buttons; clicking one replaces all with a thank-you message.
|
||||
* @param {HTMLElement} container - element containing the .feedback widget
|
||||
* @param {function} onVote - callback receiving 'up' or 'down'
|
||||
*/
|
||||
export function setupFeedback(container, onVote) {
|
||||
const widget = container.querySelector('.feedback');
|
||||
if (!widget) return;
|
||||
widget.hidden = false;
|
||||
const text = widget.querySelector('.feedback-text');
|
||||
const buttons = widget.querySelectorAll('.feedback-btn');
|
||||
const thanks = widget.querySelector('.feedback-thanks');
|
||||
text.hidden = false;
|
||||
thanks.hidden = true;
|
||||
buttons.forEach((btn) => {
|
||||
btn.hidden = false;
|
||||
btn.disabled = false;
|
||||
btn.addEventListener('click', () => {
|
||||
text.hidden = true;
|
||||
buttons.forEach((b) => { b.hidden = true; });
|
||||
thanks.hidden = false;
|
||||
onVote(btn.dataset.vote);
|
||||
}, { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a browser download of in-memory data.
|
||||
* Creates a temporary object URL, clicks a hidden <a>, then revokes it.
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* `resetNickelMenuState`.
|
||||
*/
|
||||
|
||||
import { $, $q, $qa, triggerDownload, renderNmCheckboxList, populateList } from '../dom.js';
|
||||
import { $, $q, $qa, triggerDownload, renderNmCheckboxList, populateList, setupFeedback } from '../dom.js';
|
||||
import { showStep, setNavStep } from '../nav.js';
|
||||
import { ALL_FEATURES } from '../../nickelmenu/installer.js';
|
||||
import { TL } from '../strings.js';
|
||||
import { track } from '../analytics.js';
|
||||
import { isEnabled as analyticsEnabled, track } from '../analytics.js';
|
||||
|
||||
export function initNickelMenu(state) {
|
||||
|
||||
@@ -366,6 +366,12 @@ export function initNickelMenu(state) {
|
||||
track('flow-end', { result: 'nm-download' });
|
||||
}
|
||||
|
||||
if (analyticsEnabled()) {
|
||||
setupFeedback(stepNmDone, (vote) => {
|
||||
track('feedback', { vote });
|
||||
});
|
||||
}
|
||||
|
||||
setNavStep(5);
|
||||
showStep(stepNmDone);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
* `updatePatchCount`, and `configureFirmwareStep`.
|
||||
*/
|
||||
|
||||
import { $, formatMB, triggerDownload, populateList } from '../dom.js';
|
||||
import { $, formatMB, triggerDownload, populateList, setupFeedback } from '../dom.js';
|
||||
import { showStep, setNavLabels, setNavStep } from '../nav.js';
|
||||
import { KoboModels } from '../services/kobo-device.js';
|
||||
import { TL } from '../strings.js';
|
||||
import { track } from '../analytics.js';
|
||||
import { isEnabled as analyticsEnabled, track } from '../analytics.js';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
export function initPatchesFlow(state) {
|
||||
@@ -249,6 +249,12 @@ export function initPatchesFlow(state) {
|
||||
downloadInstructions.hidden = true;
|
||||
existingTgzWarning.hidden = true;
|
||||
|
||||
if (analyticsEnabled()) {
|
||||
setupFeedback(stepDone, (vote) => {
|
||||
track('feedback', { vote });
|
||||
});
|
||||
}
|
||||
|
||||
setNavStep(5);
|
||||
showStep(stepDone);
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ export const TL = {
|
||||
STATUS: {
|
||||
DEVICE_RECOGNIZED: 'Your device has been recognized. You can continue to the next step!',
|
||||
NM_REMOVED_ON_REBOOT: 'NickelMenu will be removed on next reboot.',
|
||||
NM_INSTALLED: 'NickelMenu has been installed on your Kobo.',
|
||||
NM_DOWNLOAD_READY: 'Your NickelMenu package is ready to download.',
|
||||
NM_INSTALLED: 'NickelMenu has been prepared for your Kobo. To complete the installation, follow the instructions below.',
|
||||
NM_DOWNLOAD_READY: 'Your NickelMenu package is ready to download. After downloading, a list of installation steps will be displayed.',
|
||||
NM_WILL_BE_REMOVED: 'NickelMenu will be updated and marked for removal. It will uninstall itself when your Kobo reboots.',
|
||||
NM_WILL_BE_INSTALLED: 'The following will be installed on your Kobo:',
|
||||
NM_NICKEL_ROOT_TGZ: 'NickelMenu (KoboRoot.tgz)',
|
||||
|
||||
Reference in New Issue
Block a user