@ciphore/radiocli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +133 -1
- package/README.md +76 -6
- package/dist/agent/alarm-service.js +210 -0
- package/dist/agent/cli.js +193 -0
- package/dist/agent/headless-host.js +143 -0
- package/dist/agent/launcher.js +71 -0
- package/dist/agent/mcp-install.js +467 -0
- package/dist/agent/mcp-server.js +139 -0
- package/dist/agent/service.js +347 -0
- package/dist/agent/session.js +248 -0
- package/dist/alarms/active-session.js +183 -0
- package/dist/alarms/cli.js +312 -0
- package/dist/alarms/guard.js +343 -0
- package/dist/alarms/inhibitor.js +48 -0
- package/dist/alarms/power-guard-store.js +169 -0
- package/dist/alarms/runner.js +342 -0
- package/dist/alarms/runtime-health.js +79 -0
- package/dist/alarms/schedule.js +149 -0
- package/dist/alarms/scheduler.js +250 -0
- package/dist/alarms/setup-verification.js +187 -0
- package/dist/alarms/system-volume.js +43 -0
- package/dist/alarms/terminal-launcher.js +181 -0
- package/dist/alarms/tui-presence.js +38 -0
- package/dist/cli.js +113 -5
- package/dist/player/backend-install.js +2 -1
- package/dist/player/command-diagnostics.js +27 -0
- package/dist/player/command.js +123 -62
- package/dist/player/player-controller.js +32 -2
- package/dist/providers/provider-manager.js +5 -0
- package/dist/providers/radio-browser.js +36 -6
- package/dist/setup.js +462 -0
- package/dist/storage/store.js +262 -2
- package/dist/types.js +6 -0
- package/dist/ui/AdaptiveContent.js +111 -26
- package/dist/ui/App.js +401 -67
- package/dist/ui/AppContent.js +24 -7
- package/dist/ui/adaptive-explore-layout.js +47 -0
- package/dist/ui/alarm-editor.js +174 -0
- package/dist/ui/alarm-tui-service.js +84 -0
- package/dist/ui/app-state.js +3 -0
- package/dist/ui/ascii.js +8 -0
- package/dist/ui/components/AdaptiveMarquee.js +28 -0
- package/dist/ui/components/StationList.js +10 -5
- package/dist/ui/components/VersionIndicator.js +19 -0
- package/dist/ui/cosmo-world-map.js +5 -2
- package/dist/ui/explore-map-layout.js +18 -6
- package/dist/ui/format.js +21 -0
- package/dist/ui/help-content.js +14 -2
- package/dist/ui/layout.js +1 -1
- package/dist/ui/page-footer.js +120 -2
- package/dist/ui/receiver-animation.js +68 -0
- package/dist/ui/screen-items.js +41 -9
- package/dist/ui/screen-meta.js +4 -0
- package/dist/ui/screens/AlarmsScreen.js +202 -0
- package/dist/ui/screens/CountriesScreen.js +8 -5
- package/dist/ui/screens/ExploreScreen.js +8 -3
- package/dist/ui/screens/HomeScreen.js +3 -1
- package/dist/ui/screens/NowPlayingScreen.js +6 -2
- package/dist/ui/screens/SettingsScreen.js +70 -53
- package/dist/ui/screens/StationScreen.js +3 -2
- package/dist/ui/selection-state.js +10 -0
- package/dist/ui/terminal-mouse.js +18 -3
- package/dist/ui/use-alarm-tui.js +727 -0
- package/dist/ui/use-app-input.js +107 -46
- package/dist/ui/visualizers/gallop.js +118 -0
- package/dist/ui/visualizers/horse-stride.js +20 -0
- package/dist/ui/visualizers/receiver-style-registry.js +14 -7
- package/dist/ui/visualizers/receiver-visualizers.js +233 -128
- package/dist/ui/visualizers/retro-receivers.js +4 -0
- package/dist/ui/visualizers/terminal-receivers.js +57 -0
- package/dist/update-check.js +26 -7
- package/package.json +6 -1
|
@@ -6,12 +6,14 @@ import { panelBorderStyle, useDisplay } from '../display-context.js';
|
|
|
6
6
|
import { toAsciiSafe } from '../ascii.js';
|
|
7
7
|
import { ScreenHeader } from '../components/ScreenHeader.js';
|
|
8
8
|
import { buildVisualizer, visualizerHeight } from '../visualizers/receiver-visualizers.js';
|
|
9
|
+
import { useReceiverPulse } from '../receiver-animation.js';
|
|
9
10
|
// Terminal rows are roughly twice as tall as columns are wide. One vertical
|
|
10
11
|
// row and three horizontal columns produce a closer optical inset around the
|
|
11
12
|
// highest and lowest visible text than a mechanically equal cell count.
|
|
12
13
|
const receiverPaddingX = 3;
|
|
13
14
|
const receiverPaddingY = 1;
|
|
14
|
-
export function NowPlayingScreen({ station, playback, metadata, theme, favorite,
|
|
15
|
+
export function NowPlayingScreen({ station, playback, metadata, theme, favorite, diagnostics, showDiagnostics, stationTime, receiverStyle, trackHistory, width, height }) {
|
|
16
|
+
const pulse = useReceiverPulse();
|
|
15
17
|
const { panel: panelBackground, ascii } = useDisplay();
|
|
16
18
|
// In ASCII mode route every rendered string through the glyph mapper so no
|
|
17
19
|
// braille, block, box-drawing, or punctuation (·, ★) leaks to the terminal.
|
|
@@ -75,7 +77,9 @@ export function receiverStationIdentity(station, width) {
|
|
|
75
77
|
function renderSegments(segments) {
|
|
76
78
|
let offset = 0;
|
|
77
79
|
return segments.map(segment => {
|
|
78
|
-
|
|
80
|
+
// Keep identity tied to geometry, not animated color. Color-bearing keys
|
|
81
|
+
// forced React to unmount and recreate hundreds of Text nodes per frame.
|
|
82
|
+
const key = offset;
|
|
79
83
|
offset += segment.text.length;
|
|
80
84
|
return (_jsx(Text, { color: segment.color, backgroundColor: segment.backgroundColor, bold: segment.bold, children: segment.text }, key));
|
|
81
85
|
});
|
|
@@ -1,73 +1,68 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { defaultAgentControlSettings } from '../../types.js';
|
|
3
4
|
import { Menu, Pointer } from '../components/Menu.js';
|
|
4
5
|
import { ScreenHeader } from '../components/ScreenHeader.js';
|
|
5
6
|
import { truncate } from '../format.js';
|
|
6
|
-
import {
|
|
7
|
+
import { settingsGroup, settingsGroups, settingsItemsForPage } from '../screen-items.js';
|
|
7
8
|
import { textMuted, themeAccent } from '../theme.js';
|
|
8
9
|
import { playbackBackendCapabilities } from '../../player/backend-install.js';
|
|
9
10
|
import { airPlayReceiverSettingValue } from '../airplay-settings.js';
|
|
10
11
|
import { audioOutputLabel, audioOutputSettingValue } from '../audio-output.js';
|
|
11
12
|
import { updateStatusText } from '../../update-check.js';
|
|
12
|
-
export function SettingsScreen({ selected, settings, appVersion, updateCheck, storePath, playback, backends, airPlayDevices, providerHealth, theme, diagnostics, width, height }) {
|
|
13
|
+
export function SettingsScreen({ page = 'root', selected, settings, appVersion, updateCheck, storePath, playback, backends, airPlayDevices, providerHealth, theme, diagnostics, width, height }) {
|
|
13
14
|
const accent = themeAccent(theme);
|
|
14
15
|
const lineWidth = Math.max(32, width - 4);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
const group = settingsGroup(page);
|
|
17
|
+
const pageItems = settingsItemsForPage(page);
|
|
18
|
+
const menuWindow = settingsMenuWindow(pageItems, selected, height);
|
|
19
|
+
const selectedItem = pageItems[selected];
|
|
20
|
+
const detail = page === 'root' && selectedItem !== 'Check for updates'
|
|
21
|
+
? settingsGroupsDescription(selectedItem)
|
|
22
|
+
: settingDetail({
|
|
23
|
+
item: selectedItem,
|
|
24
|
+
appVersion,
|
|
25
|
+
updateCheck,
|
|
26
|
+
storePath,
|
|
27
|
+
playback,
|
|
28
|
+
providerHealth,
|
|
29
|
+
diagnostics,
|
|
30
|
+
lineWidth
|
|
31
|
+
});
|
|
28
32
|
const valueGap = 3;
|
|
29
33
|
const labelWidth = Math.min(34, Math.max(20, lineWidth - valueGap - 24));
|
|
30
34
|
const valueWidth = Math.max(12, Math.min(34, lineWidth - labelWidth - valueGap - 2));
|
|
31
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ScreenHeader, { title: "Settings", subtitle:
|
|
32
|
-
const label = settingLabel(item
|
|
33
|
-
const value =
|
|
34
|
-
|
|
35
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ScreenHeader, { title: "Settings", subtitle: group ? `Settings › ${group.label}` : 'Choose a category', width: width, theme: theme }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Menu, { items: menuWindow.items, selected: menuWindow.selectedOffset, keyFor: item => item, render: (item, _index, active) => {
|
|
36
|
+
const label = settingLabel(item, updateCheck, appVersion);
|
|
37
|
+
const value = page === 'root'
|
|
38
|
+
? settingsRootValue(item)
|
|
39
|
+
: settingValue(item, settings, diagnostics, backends, airPlayDevices, updateCheck, appVersion);
|
|
40
|
+
return (_jsxs(Box, { width: lineWidth, children: [_jsx(Pointer, { active: active }), _jsx(Box, { width: labelWidth, children: _jsx(Text, { color: active ? accent : undefined, bold: active, children: truncate(label, labelWidth) }) }), _jsx(Box, { width: valueGap }), _jsx(Box, { width: valueWidth, children: _jsx(Text, { color: value ? accent : textMuted, children: truncate(value ?? '', valueWidth) }) })] }));
|
|
35
41
|
} }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: textMuted, bold: true, children: "Selected" }), _jsx(Text, { color: textMuted, children: truncate(detail, lineWidth) })] })] }));
|
|
36
42
|
}
|
|
37
|
-
function settingsMenuWindow(selected, height) {
|
|
43
|
+
function settingsMenuWindow(items, selected, height) {
|
|
38
44
|
const reservedRows = 6;
|
|
39
45
|
const maxRows = height ? Math.max(5, height - reservedRows) : Number.POSITIVE_INFINITY;
|
|
40
|
-
const clampedSelected = Math.min(Math.max(selected, 0),
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
let previousSection = '';
|
|
45
|
-
settingsItems.forEach((item, index) => {
|
|
46
|
-
const section = settingsSectionFor(item);
|
|
47
|
-
let showSection = section !== previousSection;
|
|
48
|
-
let rowCost = 1 + (showSection ? 1 : 0);
|
|
49
|
-
if (page.length > 0 && pageRows + rowCost > maxRows) {
|
|
50
|
-
pages.push(page);
|
|
51
|
-
page = [];
|
|
52
|
-
pageRows = 0;
|
|
53
|
-
previousSection = '';
|
|
54
|
-
showSection = true;
|
|
55
|
-
rowCost = 2;
|
|
56
|
-
}
|
|
57
|
-
page.push({ item, section, showSection, index });
|
|
58
|
-
pageRows += rowCost;
|
|
59
|
-
previousSection = section;
|
|
60
|
-
});
|
|
61
|
-
if (page.length > 0) {
|
|
62
|
-
pages.push(page);
|
|
63
|
-
}
|
|
64
|
-
const selectedPage = pages.find(candidate => candidate.some(entry => entry.index === clampedSelected)) ?? pages[0] ?? [];
|
|
46
|
+
const clampedSelected = Math.min(Math.max(selected, 0), items.length - 1);
|
|
47
|
+
const pageSize = Number.isFinite(maxRows) ? Math.max(1, Math.floor(maxRows)) : items.length;
|
|
48
|
+
const pageStart = Math.floor(clampedSelected / pageSize) * pageSize;
|
|
49
|
+
const selectedPage = items.slice(pageStart, pageStart + pageSize);
|
|
65
50
|
return {
|
|
66
|
-
items: selectedPage,
|
|
67
|
-
selectedOffset:
|
|
51
|
+
items: [...selectedPage],
|
|
52
|
+
selectedOffset: clampedSelected - pageStart
|
|
68
53
|
};
|
|
69
54
|
}
|
|
70
|
-
|
|
55
|
+
function settingsRootValue(item) {
|
|
56
|
+
const group = settingsGroupByLabel(item);
|
|
57
|
+
return group ? `${group.items.length} settings ›` : undefined;
|
|
58
|
+
}
|
|
59
|
+
function settingsGroupByLabel(label) {
|
|
60
|
+
return settingsGroups.find(group => group.label === label);
|
|
61
|
+
}
|
|
62
|
+
function settingsGroupsDescription(label) {
|
|
63
|
+
return label ? settingsGroupByLabel(label)?.description ?? 'Press Enter to open this category.' : '';
|
|
64
|
+
}
|
|
65
|
+
export function settingValue(item, settings, diagnostics, backends, airPlayDevices, updateCheck, currentVersion) {
|
|
71
66
|
switch (item) {
|
|
72
67
|
case 'Cycle display color':
|
|
73
68
|
return settings.theme;
|
|
@@ -100,8 +95,18 @@ export function settingValue(item, settings, diagnostics, backends, airPlayDevic
|
|
|
100
95
|
return settings.reduceMotion ? 'on' : 'off';
|
|
101
96
|
case 'Mouse and trackpad scrolling':
|
|
102
97
|
return settings.mouseSupport === false ? 'off' : 'auto';
|
|
98
|
+
case 'Automatically check for updates':
|
|
99
|
+
return settings.automaticUpdateChecks === false ? 'off' : 'on';
|
|
100
|
+
case 'Allow local agent control':
|
|
101
|
+
return (settings.agentControl ?? defaultAgentControlSettings).enabled ? 'on' : 'off';
|
|
102
|
+
case 'Install or repair MCP integrations':
|
|
103
|
+
return 'run repair';
|
|
104
|
+
case 'Open TUI for agent playback':
|
|
105
|
+
return (settings.agentControl ?? defaultAgentControlSettings).openUiOnPlay ? 'on' : 'off';
|
|
106
|
+
case 'Show Now Playing for agent playback':
|
|
107
|
+
return (settings.agentControl ?? defaultAgentControlSettings).focusNowPlaying ? 'on' : 'off';
|
|
103
108
|
case 'Check for updates':
|
|
104
|
-
return updateStatusText(updateCheck);
|
|
109
|
+
return updateStatusText(updateCheck, currentVersion);
|
|
105
110
|
case 'Reset learned media keys':
|
|
106
111
|
return `prev ${settings.mediaKeys.previous.length} · play ${settings.mediaKeys.playPause.length} · next ${settings.mediaKeys.next.length}`;
|
|
107
112
|
case 'Export preferences and library':
|
|
@@ -112,9 +117,9 @@ export function settingValue(item, settings, diagnostics, backends, airPlayDevic
|
|
|
112
117
|
return undefined;
|
|
113
118
|
}
|
|
114
119
|
}
|
|
115
|
-
export function settingLabel(item, updateCheck) {
|
|
116
|
-
if (item === 'Check for updates'
|
|
117
|
-
return 'Install update';
|
|
120
|
+
export function settingLabel(item, updateCheck, currentVersion) {
|
|
121
|
+
if (item === 'Check for updates') {
|
|
122
|
+
return updateStatusText(updateCheck, currentVersion).endsWith('available') ? 'Install update' : 'Check now';
|
|
118
123
|
}
|
|
119
124
|
return settingDisplayLabels[item] ?? item;
|
|
120
125
|
}
|
|
@@ -135,7 +140,10 @@ function settingDetail(input) {
|
|
|
135
140
|
return health.length ? health.map(([provider, status]) => `${provider}: ${status}`).join(' · ') : 'Press Enter to check station providers.';
|
|
136
141
|
}
|
|
137
142
|
if (item === 'Check for updates') {
|
|
138
|
-
return `RadioCLI v${appVersion} · ${updateStatusText(updateCheck)}`;
|
|
143
|
+
return `RadioCLI v${appVersion} · ${updateStatusText(updateCheck, appVersion)}`;
|
|
144
|
+
}
|
|
145
|
+
if (item === 'Automatically check for updates') {
|
|
146
|
+
return 'When enabled, RadioCLI checks the registry once whenever the app launches.';
|
|
139
147
|
}
|
|
140
148
|
if (item === 'Export preferences and library' || item === 'Import preferences and library') {
|
|
141
149
|
return `Library file: ${truncate(storePath, Math.max(8, lineWidth - 14))}`;
|
|
@@ -149,5 +157,14 @@ function settingDetail(input) {
|
|
|
149
157
|
if (item === 'Reduce motion') {
|
|
150
158
|
return 'Freezes animated receiver displays for calmer, lower-power rendering.';
|
|
151
159
|
}
|
|
160
|
+
if (item === 'Allow local agent control') {
|
|
161
|
+
return 'Press Enter to set up or remove agent and Codex Voice control across detected clients.';
|
|
162
|
+
}
|
|
163
|
+
if (item === 'Install or repair MCP integrations') {
|
|
164
|
+
return 'Checks and repairs every detected client. Restart open Codex or agent clients afterward.';
|
|
165
|
+
}
|
|
166
|
+
if (item === 'Open TUI for agent playback') {
|
|
167
|
+
return 'Opt in to a separate terminal window. On macOS, the agent app must approve a one-time Automation prompt.';
|
|
168
|
+
}
|
|
152
169
|
return 'Press Enter to change this setting.';
|
|
153
170
|
}
|
|
@@ -3,9 +3,10 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import { StationList } from '../components/StationList.js';
|
|
4
4
|
import { ScreenHeader } from '../components/ScreenHeader.js';
|
|
5
5
|
import { textMuted } from '../theme.js';
|
|
6
|
-
|
|
6
|
+
import { truncate } from '../format.js';
|
|
7
|
+
export function StationScreen({ title, subtitle, stations, selected, loading, error, theme, favorites, filterLabel, pageSize, width }) {
|
|
7
8
|
const empty = stationEmptyState(title);
|
|
8
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ScreenHeader, { title: title, subtitle: subtitle, width: width, theme: theme, right: filterLabel === 'none' ? undefined : `filters: ${filterLabel}` }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [loading ? _jsx(Text, { color: textMuted, children: "Loading stations\u2026" }) : null, !loading || stations.length > 0 ? (_jsx(StationList, { stations: stations, selected: selected, theme: theme, favorites: favorites, pageSize: pageSize, width: width, emptyTitle: empty.title, emptyHint: empty.hint })) : null] })] }));
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ScreenHeader, { title: title, subtitle: subtitle, width: width, theme: theme, right: filterLabel === 'none' ? undefined : `filters: ${filterLabel}` }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [loading ? _jsx(Text, { color: textMuted, children: "Loading stations\u2026" }) : null, !loading && error ? _jsx(Text, { color: textMuted, children: truncate(error, width) }) : null, (!loading || stations.length > 0) && (stations.length > 0 || !error) ? (_jsx(StationList, { stations: stations, selected: selected, theme: theme, favorites: favorites, pageSize: pageSize, width: width, emptyTitle: empty.title, emptyHint: empty.hint })) : null] })] }));
|
|
9
10
|
}
|
|
10
11
|
function stationEmptyState(title) {
|
|
11
12
|
if (title.toLowerCase().includes('library')) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { clamp } from './app-state.js';
|
|
2
|
+
export function commitImmediateSelection(selectionRef, setSelection, next, itemCount) {
|
|
3
|
+
const selected = clamp(next, itemCount - 1);
|
|
4
|
+
// Update the event-facing value before React's next render. Slow terminals
|
|
5
|
+
// can deliver Enter immediately after an arrow key while rendered props are
|
|
6
|
+
// still one selection behind.
|
|
7
|
+
selectionRef.current = selected;
|
|
8
|
+
setSelection(selected);
|
|
9
|
+
return selected;
|
|
10
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cosmoCoordinateForCell } from './cosmo-world-map.js';
|
|
2
2
|
import { computeExploreMapLayout } from './explore-map-layout.js';
|
|
3
|
+
import { adaptiveExploreFrameMetrics, computeAdaptiveExploreLayout } from './adaptive-explore-layout.js';
|
|
3
4
|
export const enableMouseReporting = '\u001B[?1000h\u001B[?1006h';
|
|
4
5
|
export const disableMouseReporting = '\u001B[?1006l\u001B[?1000l';
|
|
5
6
|
const sgrMousePattern = /\u001B\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
@@ -32,7 +33,8 @@ export function parseTerminalMouseEvents(input) {
|
|
|
32
33
|
}
|
|
33
34
|
const selectableMouseScreens = new Set([
|
|
34
35
|
'home', 'countries', 'map', 'stations', 'search', 'nearby', 'explore',
|
|
35
|
-
'library', 'settings', 'help', 'airplay-settings'
|
|
36
|
+
'library', 'settings', 'help', 'airplay-settings', 'alarms', 'alarm-editor',
|
|
37
|
+
'alarm-picker', 'alarm-ringing'
|
|
36
38
|
]);
|
|
37
39
|
export function shouldEnableMouseReporting(screen, itemCount, visibleRows, mouseSupport = true) {
|
|
38
40
|
if (!mouseSupport || !selectableMouseScreens.has(screen))
|
|
@@ -60,14 +62,27 @@ export function wheelScrollDelta(events) {
|
|
|
60
62
|
}
|
|
61
63
|
export function exploreCursorForMouseCell(x, y, frameWidth, layout) {
|
|
62
64
|
if (layout.compact) {
|
|
63
|
-
|
|
65
|
+
const mode = layout.mode === 'micro' ? 'micro' : 'compact';
|
|
66
|
+
const frame = adaptiveExploreFrameMetrics(mode, layout.contentRows);
|
|
67
|
+
if (frame.bodyRows <= 0)
|
|
68
|
+
return null;
|
|
69
|
+
const mapLayout = computeAdaptiveExploreLayout(mode, frameWidth, frame.bodyRows);
|
|
70
|
+
const contentLeft = layout.horizontalPadding + 1;
|
|
71
|
+
const mapInnerLeft = contentLeft + mapLayout.mapOffsetX;
|
|
72
|
+
const mapInnerTop = 1 + frame.headerRows + frame.headerGap;
|
|
73
|
+
const col = Math.floor(x - mapInnerLeft);
|
|
74
|
+
const row = Math.floor(y - mapInnerTop);
|
|
75
|
+
if (col < 0 || col >= mapLayout.mapColumns || row < 0 || row >= mapLayout.mapRows)
|
|
76
|
+
return null;
|
|
77
|
+
const coordinate = cosmoCoordinateForCell(col, row, mapLayout.mapColumns, mapLayout.mapRows);
|
|
78
|
+
return { latitude: coordinate.lat, longitude: coordinate.lon };
|
|
64
79
|
}
|
|
65
80
|
const mapLayout = computeExploreMapLayout(frameWidth, layout.contentRows, layout.stationRows);
|
|
66
81
|
const contentLeft = 2;
|
|
67
82
|
const contentTop = layout.topRows + 1;
|
|
68
83
|
const mapOuterLeft = contentLeft;
|
|
69
84
|
const mapOuterTop = contentTop + mapLayout.headerRows + 1;
|
|
70
|
-
const mapInnerLeft = mapOuterLeft + 1;
|
|
85
|
+
const mapInnerLeft = mapOuterLeft + 1 + mapLayout.mapOffsetX;
|
|
71
86
|
const mapInnerTop = mapOuterTop + 1;
|
|
72
87
|
const col = Math.floor(x - mapInnerLeft);
|
|
73
88
|
const row = Math.floor(y - mapInnerTop);
|