Skip to content
32 changes: 19 additions & 13 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,28 @@ <h2>Israeli airfields and waypoints</h2>
}());
</script>

<!-- Quick action row: 5 most-used buttons, always visible above the
collapsible sections so they work even when sections are collapsed.
Order: build → review → export (✏️ → 📝 → ⌖ → 📋 → ⬇). -->
<div id="tb-quick">
<button id="tool-add" class="tool" data-i18n="tbAddWp" data-i18n-title="tbAddWpTitle"></button>
<button id="tool-note" class="tool" data-i18n="tbAddNote" data-i18n-title="tbAddNoteTitle"></button>
<button id="fit" data-i18n="tbFit" data-i18n-title="tbFitTitle"></button>
<button id="plan" data-i18n="tbPlan" data-i18n-title="tbPlanTitle"></button>
<button id="save" data-i18n="tbExport" data-i18n-title="tbExportTitle"></button>
</div>

<!-- Build: everything you do to construct or edit a route. -->
<div class="tb-section" data-sec="build">
<div class="tb-section-head" tabindex="0" role="button" data-i18n="tbSecBuild"></div>
<div class="tb-section-body">
<button id="tool-add" class="tool" data-i18n="tbAddWp" data-i18n-title="tbAddWpTitle"></button>
<button id="tool-note" class="tool" data-i18n="tbAddNote" data-i18n-title="tbAddNoteTitle"></button>
<button id="search-trigger" type="button" data-i18n="tbSearchOpen" data-i18n-title="tbSearchOpenTitle"></button>
<button id="reverse" data-i18n="tbReverse" data-i18n-title="tbReverseTitle"></button>
<button id="undo" data-i18n="tbUndo" data-i18n-title="tbUndoTitle" disabled></button>
<button id="clear" data-i18n="tbClear" data-i18n-title="tbClearTitle"></button>
<button id="fit" data-i18n="tbFit" data-i18n-title="tbFitTitle"></button>
<button id="tool-reset-all-markers" type="button" data-i18n="tbResetAllMarkers" data-i18n-title="tbResetAllMarkersTitle"></button>
<button id="tool-reset-all-wp-names" type="button" data-i18n="tbResetAllWpNames" data-i18n-title="tbResetAllWpNamesTitle"></button>
</div>
<div class="tb-section-body">
<button id="search-trigger" type="button" data-i18n="tbSearchOpen" data-i18n-title="tbSearchOpenTitle"></button>
<button id="reverse" data-i18n="tbReverse" data-i18n-title="tbReverseTitle"></button>
<button id="undo" data-i18n="tbUndo" data-i18n-title="tbUndoTitle" disabled></button>
<button id="clear" data-i18n="tbClear" data-i18n-title="tbClearTitle"></button>
<button id="tool-reset-all-markers" type="button" data-i18n="tbResetAllMarkers" data-i18n-title="tbResetAllMarkersTitle"></button>
<button id="tool-reset-all-wp-names" type="button" data-i18n="tbResetAllWpNames" data-i18n-title="tbResetAllWpNamesTitle"></button>
</div>
</div>

<!-- View: chart appearance + every overlay/toggle/slider. -->
Expand Down Expand Up @@ -245,7 +253,6 @@ <h2>Israeli airfields and waypoints</h2>
<div class="tb-section" data-sec="charts">
<div class="tb-section-head" tabindex="0" role="button" data-i18n="tbSecCharts"></div>
<div class="tb-section-body">
<button id="plan" data-i18n="tbPlan" data-i18n-title="tbPlanTitle"></button>
<button id="charts" data-i18n="tbCharts" data-i18n-title="tbChartsTitle"></button>
</div>
</div>
Expand All @@ -254,7 +261,6 @@ <h2>Israeli airfields and waypoints</h2>
<div class="tb-section" data-sec="export">
<div class="tb-section-head" tabindex="0" role="button" data-i18n="tbSecExport"></div>
<div class="tb-section-body">
<button id="save" data-i18n="tbExport" data-i18n-title="tbExportTitle"></button>
<button id="gpx" data-i18n="tbGpxExport" data-i18n-title="tbGpxExportTitle"></button>
<button id="load" data-i18n="tbImport" data-i18n-title="tbImportTitle"></button>
<button id="share" data-i18n="tbShare" data-i18n-title="tbShareTitle"></button>
Expand Down
12 changes: 12 additions & 0 deletions docs/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,18 @@ html, body {
}
.blink-warn { animation: blink-warn 1.2s ease-in-out infinite; }

/* Quick-action row at the top of the toolbar (#191). Always visible so the
* 5 most-used actions don't require expanding a section. */
#tb-quick {
display: flex;
flex-direction: column;
gap: 4px;
padding-bottom: 4px;
border-bottom: 1px solid #3a3636;
margin-bottom: 2px;
}
#tb-quick button { font-weight: 600; }

/* magnifying glass */
#tool-magnifier { font-size: 18px; line-height: 1; padding: 2px 6px; }
#tool-magnifier.active { background: #3a3636; border-color: #ffcc33; color: #ffcc33; }
Expand Down
141 changes: 141 additions & 0 deletions tests/quick-action-row.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// @ts-check
// PR #245 — Quick-action row at the top of the toolbar: 5 most-used buttons
// are always visible above the collapsible sections, with no duplicate IDs.
const { test, expect } = require('./_setup');

async function boot(page) {
await page.addInitScript(() => {
try {
if (localStorage.getItem('__test_qar_init') !== '1') {
localStorage.clear();
sessionStorage.clear();
localStorage.setItem('__test_qar_init', '1');
}
} catch (e) {}
});
await page.goto('/?lang=en');
await page.waitForFunction(() => typeof state !== 'undefined');
}

test.describe('Quick-action row (#tb-quick)', () => {
test('exists with the 5 expected buttons', async ({ page }) => {
await boot(page);
const quick = page.locator('#tb-quick');
await expect(quick).toBeVisible();

const ids = await quick.locator('button').evaluateAll(btns => btns.map(b => b.id));
expect(ids).toEqual(['tool-add', 'tool-note', 'fit', 'plan', 'save']);
});

test('is not a collapsible section (no .tb-section class, no header)', async ({ page }) => {
await boot(page);
const quick = page.locator('#tb-quick');
await expect(quick).not.toHaveClass(/tb-section/);
await expect(quick.locator('.tb-section-head')).toHaveCount(0);
});

test('always visible when toolbar is expanded', async ({ page }) => {
await boot(page);
await expect(page.locator('#tb-quick')).toBeVisible();
});

test.describe('no duplicate IDs — buttons removed from collapsible sections', () => {
test('tool-add and tool-note not in Build section', async ({ page }) => {
await boot(page);
const build = page.locator('.tb-section[data-sec="build"]');
await expect(build.locator('#tool-add')).toHaveCount(0);
await expect(build.locator('#tool-note')).toHaveCount(0);
});

test('fit not in Build section', async ({ page }) => {
await boot(page);
const build = page.locator('.tb-section[data-sec="build"]');
await expect(build.locator('#fit')).toHaveCount(0);
});

test('plan not in Charts section', async ({ page }) => {
await boot(page);
const charts = page.locator('.tb-section[data-sec="charts"]');
await expect(charts.locator('#plan')).toHaveCount(0);
});

test('save not in Export section', async ({ page }) => {
await boot(page);
const exportSec = page.locator('.tb-section[data-sec="export"]');
await expect(exportSec.locator('#save')).toHaveCount(0);
});
});

test.describe('CSS styling', () => {
test('buttons have font-weight 600', async ({ page }) => {
await boot(page);
const btn = page.locator('#tb-quick button').first();
const fw = await btn.evaluate(el => getComputedStyle(el).fontWeight);
expect(fw).toBe('600');
});

test('row has a bottom border divider', async ({ page }) => {
await boot(page);
const quick = page.locator('#tb-quick');
const bb = await quick.evaluate(el => getComputedStyle(el).borderBottom);
expect(bb).toMatch(/1px/);
});
});

test.describe('quick-action buttons are functional', () => {
test('tool-add enters add-waypoint mode', async ({ page }) => {
await boot(page);
await page.locator('#tb-quick #tool-add').click();
const hasClass = await page.locator('#map').evaluate(el => el.classList.contains('add'));
expect(hasClass).toBe(true);
// Exit mode
await page.keyboard.press('Escape');
});

test('tool-note enters add-note mode', async ({ page }) => {
await boot(page);
await page.locator('#tb-quick #tool-note').click();
const hasAddClass = await page.locator('#map').evaluate(el => el.classList.contains('add'));
expect(hasAddClass).toBe(true);
const isActive = await page.locator('#tb-quick #tool-note').evaluate(el => el.classList.contains('active'));
expect(isActive).toBe(true);
await page.keyboard.press('Escape');
});

test('fit triggers fitView', async ({ page }) => {
await boot(page);
await page.evaluate(() => {
state.waypoints.push({ lat: 32.0, lng: 34.8, name: 'A' });
state.waypoints.push({ lat: 32.1, lng: 34.9, name: 'B' });
});
await page.locator('#tb-quick #fit').click();
const center = await page.evaluate(() => map.getCenter());
expect(center.lat).toBeCloseTo(32.05, 1);
});

test('plan toggles flight plan modal', async ({ page }) => {
await boot(page);
await page.evaluate(() => {
state.waypoints.push({ lat: 32.0, lng: 34.8, name: 'A' });
state.waypoints.push({ lat: 32.1, lng: 34.9, name: 'B' });
syncLegs();
draw();
});
await page.locator('#tb-quick #plan').click();
await expect(page.locator('.modal-back.flight-plan')).toBeVisible();
await page.locator('#tb-quick #plan').click();
await expect(page.locator('.modal-back.flight-plan')).not.toBeVisible();
});

test('save triggers JSON export', async ({ page }) => {
await boot(page);
await page.evaluate(() => {
state.waypoints.push({ lat: 32.0, lng: 34.8, name: 'A' });
state.waypoints.push({ lat: 32.1, lng: 34.9, name: 'B' });
});
const download = page.waitForEvent('download', { timeout: 3000 });
await page.locator('#tb-quick #save').click();
await download;
});
});
});
Loading