feat(appearance): colour log rows by QSL status, and fix UDP QSO numbering
New Settings → Appearance section: whole-row colouring in the log grid driven by QSL / LoTW state, each rule with its own colour from a palette or a free picker. What Logger32 does, with one difference that matters — the colour is applied as a 24% tint, not a fill. Logger32's grid is white; this one is dark, and a saturated user-picked colour behind white text is unreadable at exactly the moment the operator is scanning for what still needs sending. Rules are ORDERED and the first match wins, because a contact is usually several of these at once: one confirmed on LoTW and by card is confirmed, not "sent, awaiting reply". The order lives in the data so the panel can show the rules in the order they actually apply, numbered. The colour is interpolated into a CSS color-mix(), so anything that is not plainly #rrggbb is refused on the way in and falls back to the default. Also fixes the QSO number column, which was empty for contacts logged from WSJT-X: that path inserts through the repo directly and never reached AddQSO. Rather than chase each of the remaining bulk-insert paths — the POTA hunter import and the LoTW/QRZ "add what I was missing" passes, which insert OLD dates and so shift every number after them — the index now checks its length against a COUNT and rebuilds when they disagree. One indexed count beats remembering to invalidate in a place that does not exist yet.
This commit is contained in:
@@ -91,6 +91,7 @@ import { ShutdownProgress } from '@/components/ShutdownProgress';
|
||||
import { ClusterGrid } from '@/components/ClusterGrid';
|
||||
import { cleanSpotter, inferSpotMode, spotModeCategory, spotStatusKey } from '@/lib/spot';
|
||||
import { applySpotDisplay, readSpotDisplayOptions, SPOT_DISPLAY_OPTIONS_EXPOSED } from '@/lib/spotDisplay';
|
||||
import { GetRowColors } from '../wailsjs/go/main/App';
|
||||
import { WorkedBeforeGrid } from '@/components/WorkedBeforeGrid';
|
||||
import { NetControlPanel } from '@/components/NetControlPanel';
|
||||
import { ContestPanel, CONTEST_DEFAULT, type ContestSession } from '@/components/ContestPanel';
|
||||
@@ -1923,6 +1924,10 @@ export default function App() {
|
||||
// change). The grid reads them straight from the row — no per-page backend
|
||||
// recompute — so here we just parse the stored JSON string into the code→ref
|
||||
// object the award columns expect (keys are already upper-case).
|
||||
// Row colouring by QSL status (Settings → Appearance). Reloaded when the
|
||||
// settings dialog closes, which is the only place it changes.
|
||||
const [rowColors, setRowColors] = useState<any>(null);
|
||||
useEffect(() => { GetRowColors().then(setRowColors).catch(() => {}); }, [showSettings]);
|
||||
const qsosWithAwards = useMemo(
|
||||
() => (qsos as any[]).map((q) => ({ ...q, award_refs: parseAwardRefs(q.award_refs) })),
|
||||
[qsos],
|
||||
@@ -5070,6 +5075,7 @@ export default function App() {
|
||||
// rewrote the other's widths.
|
||||
storageKey="mainpane"
|
||||
rows={qsosWithAwards as any}
|
||||
rowColors={rowColors}
|
||||
myGrid={station.my_grid}
|
||||
total={total}
|
||||
awardCols={awardCols}
|
||||
@@ -6353,6 +6359,7 @@ export default function App() {
|
||||
<RecentQSOsGrid
|
||||
key={`rqg2-${activeProfileId ?? 'x'}`}
|
||||
rows={qsosWithAwards as any}
|
||||
rowColors={rowColors}
|
||||
myGrid={station.my_grid}
|
||||
total={total}
|
||||
awardCols={awardCols}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { GetRowColors, SaveRowColors } from '../../wailsjs/go/main/App';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { RowColorSettings } from '@/lib/rowColors';
|
||||
|
||||
// A fixed palette plus a free picker. Muted values on purpose: they are
|
||||
// composited at low opacity over a dark grid, where a saturated colour reads as
|
||||
// an error state rather than a status.
|
||||
const PALETTE = [
|
||||
'#16a34a', '#0ea5e9', '#f59e0b', '#a855f7',
|
||||
'#dc2626', '#14b8a6', '#eab308', '#ec4899',
|
||||
'#64748b', '#84cc16', '#6366f1', '#f97316',
|
||||
];
|
||||
|
||||
// The rule ids the backend orders; the labels live here so a translation never
|
||||
// travels through the settings row.
|
||||
const LABELS: Record<string, string> = {
|
||||
confirmed_lotw: 'appr.confirmedLotw',
|
||||
confirmed_paper: 'appr.confirmedPaper',
|
||||
sent_waiting: 'appr.sentWaiting',
|
||||
to_send: 'appr.toSend',
|
||||
};
|
||||
|
||||
export function AppearancePanel() {
|
||||
const { t } = useI18n();
|
||||
const [cfg, setCfg] = useState<RowColorSettings | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try { setCfg((await GetRowColors()) as any); } catch { /* defaults on the backend */ }
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const save = (next: RowColorSettings) => {
|
||||
setCfg(next);
|
||||
SaveRowColors(next as any).catch(() => {});
|
||||
};
|
||||
const patchRule = (id: string, patch: Partial<{ color: string; enabled: boolean }>) => {
|
||||
if (!cfg) return;
|
||||
save({ ...cfg, rules: cfg.rules.map((r) => (r.id === id ? { ...r, ...patch } : r)) });
|
||||
};
|
||||
|
||||
if (!cfg) return <div className="p-1 text-sm text-muted-foreground">…</div>;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-start gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={cfg.enabled} className="mt-0.5"
|
||||
onCheckedChange={(c) => save({ ...cfg, enabled: !!c })} />
|
||||
<span>{t('appr.enable')} <span className="text-xs text-muted-foreground">{t('appr.enableHint')}</span></span>
|
||||
</label>
|
||||
|
||||
{cfg.enabled && (
|
||||
<div className="space-y-2">
|
||||
{/* Order matters and is shown: a contact is usually several of these at
|
||||
once, and the first match wins. */}
|
||||
<p className="text-xs text-muted-foreground">{t('appr.orderHint')}</p>
|
||||
{cfg.rules.map((r, i) => (
|
||||
<div key={r.id} className="rounded-lg border border-border/60 p-2.5 space-y-2"
|
||||
style={{ backgroundColor: r.enabled ? `color-mix(in srgb, ${r.color} 24%, transparent)` : undefined }}>
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox checked={r.enabled} onCheckedChange={(c) => patchRule(r.id, { enabled: !!c })} />
|
||||
<span className="font-mono text-xs text-muted-foreground">{i + 1}.</span>
|
||||
<span className="font-medium">{t(LABELS[r.id] ?? r.id)}</span>
|
||||
</label>
|
||||
{r.enabled && (
|
||||
<div className="flex items-center gap-1.5 flex-wrap pl-6">
|
||||
{PALETTE.map((c) => (
|
||||
<button key={c} type="button" title={c}
|
||||
onClick={() => patchRule(r.id, { color: c })}
|
||||
className={cn('size-6 rounded-md border-2 transition-transform hover:scale-110',
|
||||
r.color.toLowerCase() === c ? 'border-foreground' : 'border-transparent')}
|
||||
style={{ backgroundColor: c }} />
|
||||
))}
|
||||
<input type="color" value={r.color} title={t('appr.custom')}
|
||||
onChange={(e) => patchRule(r.id, { color: e.target.value })}
|
||||
className="size-6 rounded-md border border-border bg-transparent p-0 cursor-pointer" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { loadLocal, loadRemote, saveState, seedLocal } from '@/lib/gridPrefs';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { gridToLatLon, pathBetweenLatLon } from '@/lib/maidenhead';
|
||||
import { rowStyleFor, type RowColorSettings } from '@/lib/rowColors';
|
||||
|
||||
// Register every Community feature once. v32+ requires explicit registration;
|
||||
// AllCommunityModule keeps it simple and pulls in sort/filter/resize/reorder/
|
||||
@@ -80,6 +81,8 @@ type Props = {
|
||||
// One column per defined award; the cell shows the reference this QSO counts
|
||||
// for (from row.award_refs[CODE], attached by the parent). Hidden by default.
|
||||
awardCols?: { code: string; name: string }[];
|
||||
// Whole-row colouring by QSL / LoTW status (Settings → Appearance).
|
||||
rowColors?: RowColorSettings | null;
|
||||
};
|
||||
|
||||
const BASE_COLSTATE_KEY = 'hamlog.qsoColState.v2';
|
||||
@@ -303,7 +306,7 @@ const sanitizeAwardCols = (st: any[] | null | undefined): any[] =>
|
||||
return rest;
|
||||
});
|
||||
|
||||
export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, rowDragCall, passOrder, onGridApi, storageKey, onRowDoubleClicked, onRowClicked, onRowSelected, onRowSelectedQso, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete, onFilteredCountChange, awardCols }: Props) {
|
||||
export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal, rowDragCall, passOrder, onGridApi, storageKey, onRowDoubleClicked, onRowClicked, onRowSelected, onRowSelectedQso, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportFiltered, onExportCabrilloSelected, onExportCabrilloFiltered, onDelete, onFilteredCountChange, awardCols, rowColors }: Props) {
|
||||
const { t } = useI18n();
|
||||
const gridRef = useRef<any>(null);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
@@ -700,6 +703,7 @@ export function RecentQSOsGrid({ rows, myGrid, selectAllSignal, selectRowSignal,
|
||||
animateRows={false}
|
||||
suppressCellFocus
|
||||
getRowId={(p) => String((p.data as any).id)}
|
||||
getRowStyle={(p) => rowStyleFor(p.data, rowColors ?? null)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +77,7 @@ import { getDateFormat, setDateFormat, type DateFormat } from '@/lib/dateFormat'
|
||||
import { useI18n, FlagGB, FlagFR, type Lang } from '@/lib/i18n';
|
||||
import { useTheme, CONCRETE_THEMES, type ThemeChoice } from '@/lib/theme';
|
||||
import { OperatingPanel } from '@/components/OperatingPanel';
|
||||
import { AppearancePanel } from '@/components/AppearancePanel';
|
||||
import { UDPIntegrationsPanel } from '@/components/UDPIntegrationsPanel';
|
||||
|
||||
type LookupSettings = LookupSettingsForm;
|
||||
@@ -176,6 +177,7 @@ interface Props {
|
||||
`disabled: true` greys them out and shows the "coming soon" placeholder. */
|
||||
type SectionId =
|
||||
| 'general'
|
||||
| 'appearance'
|
||||
| 'email'
|
||||
| 'station'
|
||||
| 'profiles'
|
||||
@@ -266,6 +268,7 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[]
|
||||
{
|
||||
kind: 'group', label: t('nav.software'), icon: Cog, defaultOpen: true, children: [
|
||||
{ kind: 'item', label: t('sec.general'), id: 'general' },
|
||||
{ kind: 'item', label: t('sec.appearance'), id: 'appearance' },
|
||||
{ kind: 'item', label: t('sec.email'), id: 'email' },
|
||||
{ kind: 'item', label: t('sec.lookup'), id: 'lookup' },
|
||||
{ kind: 'group', label: t('nav.lists'), icon: Database, defaultOpen: true, children: [
|
||||
@@ -290,7 +293,7 @@ function buildTree(flexAvailable: boolean, t: (k: string) => string): TreeNode[]
|
||||
// Map section id → i18n key (breadcrumb / placeholders).
|
||||
const SECTION_KEY: Partial<Record<SectionId, string>> = {
|
||||
station: 'sec.station', profiles: 'sec.profiles', operating: 'sec.operating', confirmations: 'sec.confirmations',
|
||||
'external-services': 'sec.external', lookup: 'sec.lookup', 'lists-bands': 'sec.bands', 'lists-modes': 'sec.modes',
|
||||
'external-services': 'sec.external', appearance: 'sec.appearance', lookup: 'sec.lookup', 'lists-bands': 'sec.bands', 'lists-modes': 'sec.modes',
|
||||
cluster: 'sec.cluster', backup: 'sec.backup', database: 'sec.database', autostart: 'sec.autostart', udp: 'sec.udp',
|
||||
adifmon: 'sec.adifmon',
|
||||
webpublish: 'sec.webpublish',
|
||||
@@ -6163,6 +6166,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
// Map sections to their content + icon (for placeholder).
|
||||
const PANELS: Record<SectionId, () => JSX.Element> = {
|
||||
general: GeneralPanel,
|
||||
appearance: () => <AppearancePanel />,
|
||||
email: EmailPanel,
|
||||
station: StationPanel,
|
||||
profiles: ProfilesPanel,
|
||||
|
||||
@@ -114,7 +114,7 @@ const en: Dict = {
|
||||
'nav.user': 'User Configuration', 'nav.software': 'Software Configuration', 'nav.hardware': 'Hardware Configuration', 'nav.lists': 'Lists',
|
||||
'sec.station': 'Station Information', 'sec.profiles': 'Profiles', 'sec.operating': 'Operating conditions',
|
||||
'sec.confirmations': 'Confirmations', 'sec.external': 'External services',
|
||||
'sec.general': 'General', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup',
|
||||
'sec.general': 'General', 'sec.appearance': 'Appearance', 'appr.enable': 'Colour whole rows by QSL status', 'appr.enableHint': '(in the log grid, like Logger32)', 'appr.orderHint': 'A contact is often several of these at once — the first rule that matches decides the colour.', 'appr.confirmedLotw': 'Confirmed on LoTW', 'appr.confirmedPaper': 'Confirmed by card or eQSL', 'appr.sentWaiting': 'Sent, no answer yet', 'appr.toSend': 'Card requested, not sent', 'appr.custom': 'Pick any colour', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup',
|
||||
'sec.bands': 'Bands', 'sec.modes': 'Modes & default RST', 'sec.cluster': 'DX Cluster',
|
||||
'sec.udp': 'UDP integrations', 'sec.database': 'Database', 'sec.autostart': 'Autostart', 'sec.backup': 'Database backup', 'sec.uscounties': 'US Counties',
|
||||
'sec.webpublish': 'Web publishing', 'wpub.hint': 'Publishes your log as a file for a website: a standalone HTML page or a CSV, written locally and optionally uploaded by FTP. It is refreshed when you log a QSO and, if you set an interval, on a timer.', 'wpub.enable': 'Publish the log to a file', 'wpub.fileSection': 'The file', 'wpub.format': 'Format', 'wpub.formatHtml': 'HTML page', 'wpub.formatCsv': 'CSV', 'wpub.folder': 'Output folder', 'wpub.browse': 'Browse…', 'wpub.fileName': 'File name', 'wpub.title': 'Page title', 'wpub.titlePh': 'blank = your callsign', 'wpub.count': 'Last N QSOs', 'wpub.every': 'Refresh every', 'wpub.everyHint': 'minutes — 0 = only when a QSO is logged', 'wpub.columns': 'Columns', 'wpub.columnsCount': '{n} of {total} chosen', 'wpub.columnsPick': 'Choose columns…', 'wpub.columnsSearch': 'Search a field…', 'wpub.removeColumn': 'Click to remove', 'wpub.columnsHint': 'Click to add or remove. The order shown here is the order in the file.', 'wpub.ftpEnable': 'Upload by FTP', 'wpub.ftpHost': 'Server / port', 'wpub.ftpUser': 'User', 'wpub.ftpPassword': 'Password', 'wpub.ftpFolder': 'Remote folder', 'wpub.ftpFileName': 'Remote file name', 'wpub.ftpTls': 'Use TLS (FTPS)', 'wpub.publishNow': 'Publish now', 'wpub.testFtp': 'Test connection', 'wpub.lastRun': 'Last run:', 'sec.adifmon': 'ADIF monitor',
|
||||
@@ -541,7 +541,7 @@ const fr: Dict = {
|
||||
'nav.user': 'Configuration utilisateur', 'nav.software': 'Configuration logicielle', 'nav.hardware': 'Configuration matérielle', 'nav.lists': 'Listes',
|
||||
'sec.station': 'Informations station', 'sec.profiles': 'Profils', 'sec.operating': "Conditions d'opération",
|
||||
'sec.confirmations': 'Confirmations', 'sec.external': 'Services externes',
|
||||
'sec.general': 'Général', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif",
|
||||
'sec.general': 'Général', 'sec.appearance': 'Apparence', 'appr.enable': 'Colorer les lignes entières selon le statut QSL', 'appr.enableHint': '(dans le tableau du log, comme Logger32)', 'appr.orderHint': "Un contact est souvent plusieurs de ces états à la fois — la première règle qui correspond décide de la couleur.", 'appr.confirmedLotw': 'Confirmé sur LoTW', 'appr.confirmedPaper': 'Confirmé par carte ou eQSL', 'appr.sentWaiting': 'Envoyé, sans réponse', 'appr.toSend': 'Carte demandée, non envoyée', 'appr.custom': 'Choisir une couleur', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif",
|
||||
'sec.bands': 'Bandes', 'sec.modes': 'Modes & RST par défaut', 'sec.cluster': 'DX Cluster',
|
||||
'sec.udp': 'Intégrations UDP', 'sec.database': 'Base de données', 'sec.autostart': 'Démarrage auto', 'sec.backup': 'Sauvegarde base', 'sec.uscounties': 'Comtés US',
|
||||
'sec.webpublish': 'Publication web', 'wpub.hint': "Publie ton journal dans un fichier destiné à un site web : une page HTML autonome ou un CSV, écrit en local et envoyé par FTP si tu le souhaites. Il est rafraîchi à chaque QSO enregistré et, si tu règles un intervalle, périodiquement.", 'wpub.enable': 'Publier le journal dans un fichier', 'wpub.fileSection': 'Le fichier', 'wpub.format': 'Format', 'wpub.formatHtml': 'Page HTML', 'wpub.formatCsv': 'CSV', 'wpub.folder': 'Dossier de sortie', 'wpub.browse': 'Parcourir…', 'wpub.fileName': 'Nom du fichier', 'wpub.title': 'Titre de la page', 'wpub.titlePh': 'vide = ton indicatif', 'wpub.count': 'N derniers QSO', 'wpub.every': 'Rafraîchir toutes les', 'wpub.everyHint': 'minutes — 0 = seulement à chaque QSO', 'wpub.columns': 'Colonnes', 'wpub.columnsCount': '{n} sur {total} choisis', 'wpub.columnsPick': 'Choisir les colonnes…', 'wpub.columnsSearch': 'Chercher un champ…', 'wpub.removeColumn': 'Cliquer pour retirer', 'wpub.columnsHint': 'Clique pour ajouter ou retirer. L ordre affiché ici est celui du fichier.', 'wpub.ftpEnable': 'Envoyer par FTP', 'wpub.ftpHost': 'Serveur / port', 'wpub.ftpUser': 'Utilisateur', 'wpub.ftpPassword': 'Mot de passe', 'wpub.ftpFolder': 'Dossier distant', 'wpub.ftpFileName': 'Nom du fichier distant', 'wpub.ftpTls': 'Utiliser TLS (FTPS)', 'wpub.publishNow': 'Publier maintenant', 'wpub.testFtp': 'Tester la connexion', 'wpub.lastRun': 'Dernière exécution :', 'sec.adifmon': 'Moniteur ADIF',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Row colouring for the log grid, by QSL / LoTW status.
|
||||
//
|
||||
// The rules are ORDERED and the first match wins: a contact is usually several
|
||||
// things at once, and one confirmed on LoTW and by card is confirmed, not
|
||||
// "sent, awaiting reply".
|
||||
|
||||
export type RowColorRule = { id: string; color: string; enabled: boolean };
|
||||
export type RowColorSettings = { enabled: boolean; rules: RowColorRule[] };
|
||||
|
||||
// ADIF QSL fields are single letters. Y is the only one that means "yes";
|
||||
// R (requested) and Q (queued) mean a card is owed, which is a different state
|
||||
// and the one an operator is looking for when deciding what to post.
|
||||
const yes = (v: any) => String(v ?? '').trim().toUpperCase() === 'Y';
|
||||
const owed = (v: any) => {
|
||||
const s = String(v ?? '').trim().toUpperCase();
|
||||
return s === 'R' || s === 'Q';
|
||||
};
|
||||
|
||||
export function matchRowRule(q: any): string | null {
|
||||
if (!q) return null;
|
||||
if (yes(q.lotw_rcvd)) return 'confirmed_lotw';
|
||||
if (yes(q.qsl_rcvd) || yes(q.eqsl_rcvd)) return 'confirmed_paper';
|
||||
if (yes(q.qsl_sent) || yes(q.lotw_sent) || yes(q.eqsl_sent)) return 'sent_waiting';
|
||||
if (owed(q.qsl_sent)) return 'to_send';
|
||||
return null;
|
||||
}
|
||||
|
||||
// The colour is applied as a TINT, not a fill. The grid is dark and a solid
|
||||
// user-picked colour behind white text is unreadable at exactly the moment it
|
||||
// matters — Logger32 gets away with it because its grid is white.
|
||||
export function rowStyleFor(q: any, cfg: RowColorSettings | null): { backgroundColor: string } | undefined {
|
||||
if (!cfg?.enabled) return undefined;
|
||||
const id = matchRowRule(q);
|
||||
if (!id) return undefined;
|
||||
const rule = cfg.rules?.find((r) => r.id === id);
|
||||
if (!rule?.enabled || !rule.color) return undefined;
|
||||
return { backgroundColor: `color-mix(in srgb, ${rule.color} 24%, transparent)` };
|
||||
}
|
||||
Vendored
+4
@@ -492,6 +492,8 @@ export function GetRotatorHeading():Promise<main.RotatorHeading>;
|
||||
|
||||
export function GetRotators():Promise<Array<main.RotatorDevice>>;
|
||||
|
||||
export function GetRowColors():Promise<main.RowColorSettings>;
|
||||
|
||||
export function GetSPEStatus():Promise<spe.Status>;
|
||||
|
||||
export function GetScpStatus():Promise<main.ScpStatus>;
|
||||
@@ -928,6 +930,8 @@ export function SaveRelayAuto(arg1:main.RelayAutoConfig):Promise<void>;
|
||||
|
||||
export function SaveRotators(arg1:Array<main.RotatorDevice>):Promise<void>;
|
||||
|
||||
export function SaveRowColors(arg1:main.RowColorSettings):Promise<void>;
|
||||
|
||||
export function SaveSelfSpotSettings(arg1:main.SelfSpotSettings):Promise<void>;
|
||||
|
||||
export function SaveStationDevices(arg1:Array<main.StationDevice>):Promise<void>;
|
||||
|
||||
@@ -926,6 +926,10 @@ export function GetRotators() {
|
||||
return window['go']['main']['App']['GetRotators']();
|
||||
}
|
||||
|
||||
export function GetRowColors() {
|
||||
return window['go']['main']['App']['GetRowColors']();
|
||||
}
|
||||
|
||||
export function GetSPEStatus() {
|
||||
return window['go']['main']['App']['GetSPEStatus']();
|
||||
}
|
||||
@@ -1798,6 +1802,10 @@ export function SaveRotators(arg1) {
|
||||
return window['go']['main']['App']['SaveRotators'](arg1);
|
||||
}
|
||||
|
||||
export function SaveRowColors(arg1) {
|
||||
return window['go']['main']['App']['SaveRowColors'](arg1);
|
||||
}
|
||||
|
||||
export function SaveSelfSpotSettings(arg1) {
|
||||
return window['go']['main']['App']['SaveSelfSpotSettings'](arg1);
|
||||
}
|
||||
|
||||
@@ -2992,6 +2992,54 @@ export namespace main {
|
||||
this.motorized = source["motorized"];
|
||||
}
|
||||
}
|
||||
export class RowColorRule {
|
||||
id: string;
|
||||
color: string;
|
||||
enabled: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new RowColorRule(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.color = source["color"];
|
||||
this.enabled = source["enabled"];
|
||||
}
|
||||
}
|
||||
export class RowColorSettings {
|
||||
enabled: boolean;
|
||||
rules: RowColorRule[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new RowColorSettings(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.enabled = source["enabled"];
|
||||
this.rules = this.convertValues(source["rules"], RowColorRule);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class ScpStatus {
|
||||
enabled: boolean;
|
||||
count: number;
|
||||
|
||||
Reference in New Issue
Block a user