fix(settings): Preferences stopped lagging behind the keyboard
Typing a cluster macro cost two things per character. The state lived on SettingsModal, so every keystroke in one of those twenty-four boxes re-rendered the WHOLE preferences dialog — every list, every form, every panel. And the save wrote through to Go and into the database on each one: a round trip per character. The editor is now its own module-scoped component with its own state, so a keystroke re-renders twelve rows. And writeUiPrefDebounced holds the database write until the typing stops, while the local cache — which is what everything reads back — is still written at once. Pending values are flushed when the page goes away, so typing and immediately closing does not lose the last word. The behaviour is unchanged: still saved as you type, still no Save button, because a text box whose contents only take effect on some other button is how work gets lost. Also folds the satellite changelog into one [NEW] entry. Satellites are new in this version — nobody reading the notes has seen any of it — so a running account of how it was built, tab then tracking then rotator then where the settings moved to, is the wrong shape. One entry saying what it does.
This commit is contained in:
+6
-26
@@ -3,40 +3,20 @@
|
|||||||
"version": "0.27.17",
|
"version": "0.27.17",
|
||||||
"date": "",
|
"date": "",
|
||||||
"en": [
|
"en": [
|
||||||
"[NEW] Satellites. A new tab (Tools → Satellites) tracks the amateur birds: a map with each satellite's footprint and the selected one's path over the ground, the next passes with their maximum elevation, and — for the satellite you are on — the azimuth, the elevation and the Doppler-corrected downlink and uplink. Orbital elements come from Celestrak (with a mirror behind it) and are kept on disk, so the tab is full the moment it opens even with no internet; elements for a bird no feed carries yet can be pasted in and survive every refresh. The shipped frequency list covers the FM and linear satellites and QO-100, and lives in a file you can correct yourself when a transponder is switched.",
|
"[NEW] Satellites. A new tab (Tools → Satellites) works the amateur birds from end to end. A map with each satellite's footprint and the selected one's path over the ground; a sky plot the way every tracker draws one, centre straight up and rim at the horizon, with the whole pass and where the bird is on it; a countdown to AOS — or to LOS once it is up — with rise, peak and set, their compass directions, distance, altitude and footprint; and a pass table for everything you follow.\n\nTrack puts the radio on the satellite and keeps it there, once a second: an IC-9700 or IC-9100 in its own satellite mode, a FlexRadio on two slices (A the downlink, B the uplink, created if missing, full duplex on) — and any other rig on the downlink, which it says plainly rather than half-doing the job. Tune the receiver where you like: the tracker reads the dial, takes it as the station you have chosen, and moves the transmitter to match. An az/el rotator follows along, either driven directly over EasyComm II or handed to PstRotator if you already run it; a 450° rotator is used as one, so a pass crossing north continues instead of unwinding.\n\nQSOs made while tracking are logged with the NOMINAL frequencies, SAT_NAME, SAT_MODE and PROP_MODE=SAT — the transponder's own numbers, which both stations can agree on, rather than where one radio happened to be.\n\nOrbital elements come from Celestrak with a mirror behind it and are kept on disk, so the tab is full the moment it opens even with no internet; elements for a bird no feed carries yet can be pasted in and survive every refresh. Twenty-five satellites ship with a frequency plan — the FM and linear birds, GreenCube, QO-100 narrow and wide — in a file you can correct yourself when a transponder is switched. Everything about setting it up lives in Settings → Satellites, including which satellites you follow, chosen the way you choose awards.",
|
||||||
"Doppler tracking drives the radio. Track puts the rig on the satellite and keeps it there, once a second: an IC-9700 or IC-9100 in its own satellite mode, a FlexRadio on two slices (A the downlink, B the uplink, created if they are missing, full duplex on) — and any other radio on the downlink, which it says plainly rather than half-doing the job. Tune the receiver where you like: the tracker reads the dial, takes it as the station you have chosen, and moves the transmitter to match. QSOs made while tracking are logged with the NOMINAL frequencies, SAT_NAME, SAT_MODE and PROP_MODE=SAT — the transponder's own numbers, which both stations can agree on, rather than where one radio happened to be.",
|
|
||||||
"The antenna follows too. An EasyComm II rotator — what SatPC32, Gpredict and Hamlib speak, so most az/el controllers — is pointed at the satellite while you track, over serial or over the network (Settings → Satellites). It is a separate machine from your HF rotator, so a station with both keeps both. A 450° rotator is used as one: a pass crossing north continues past 360 instead of unwinding through the whole scale with the antenna sweeping the ground. The panel shows where the antenna actually is beside where the satellite is — and says plainly when a controller only accepts commands without reporting back, which many do.",
|
|
||||||
"Settings → Satellites is where the satellite work is set up, and the tab is what you use during a pass. Choose the satellites you follow the way you choose awards — two columns, search, the ones with a frequency plan first — and the tab and the pass list show those and nothing else. The orbital elements move there too: age, fetch, and paste-your-own. None of it is something to be doing while a bird is going over.",
|
|
||||||
"The satellite panel says what a pass actually is. A countdown to AOS, or to LOS once it is up, with a bar showing where in the pass you are; rise, peak and set with their compass directions; distance, altitude and footprint; and whether it is approaching or receding, which is why the frequencies move the way they do. The frequencies keep the corrected figure large and the Doppler shift beside it.",
|
|
||||||
"One satellite list, in one place. Settings → Lists → Satellites is gone: the birds it held by hand had nothing to do with the ones the tracker knew, and the same station kept two lists that drifted apart. The SAT_NAME box on the entry form now offers the satellites you follow — plus anything the old list still held, so nobody's typing is lost. Satellites also moved out of Hardware, where it never belonged, and sits with Operating.",
|
|
||||||
"PstRotator can point the antenna for satellites too. It handles azimuth and elevation and already knows your controller, so if you run it, choose it in Settings → Satellites and OpsLog sends it the bearing instead of taking the cable itself — two programs on one controller is one too many. Its overlap handling stays its own: a 450° rotator is PstRotator's business, not something two programs should each decide about mid-pass.",
|
|
||||||
"Each map keeps its own imagery. The world map and the grid-square map shared one setting, so choosing satellite imagery to look at grids repainted the main map as well, and there was no way to have terrain on one and plain streets on the other. All four — world, grid squares, FT map, satellites — now remember their own choice, and it travels with the data folder like the remembered views. A choice already made for the grid map is carried over, not reset.",
|
"Each map keeps its own imagery. The world map and the grid-square map shared one setting, so choosing satellite imagery to look at grids repainted the main map as well, and there was no way to have terrain on one and plain streets on the other. All four — world, grid squares, FT map, satellites — now remember their own choice, and it travels with the data folder like the remembered views. A choice already made for the grid map is carried over, not reset.",
|
||||||
"Two or three FT8 programs at once no longer fight over the callsign field. Click a station in MSHV and only MSHV has a DX Call; WSJT-X and JTDX beside it are idle and say so once a second each — and OpsLog was reading those as MSHV abandoning the station, so the entry emptied and refilled at 1 Hz and the map zoomed in and out with it. A cleared DX Call is now read per program, never across the listener; and the program that announces a station keeps the entry field until it clears its own call, is closed, or the QSO is logged.",
|
"Two or three FT8 programs at once no longer fight over the callsign field. Click a station in MSHV and only MSHV has a DX Call; WSJT-X and JTDX beside it are idle and say so once a second each — and OpsLog was reading those as MSHV abandoning the station, so the entry emptied and refilled at 1 Hz and the map zoomed in and out with it. A cleared DX Call is now read per program, never across the listener; and the program that announces a station keeps the entry field until it clears its own call, is closed, or the QSO is logged.",
|
||||||
"The satellite readout column can be resized, and hidden. Drag the grip between it and the map, double-click to reset it, or fold it away with the button in the header when the map is what you want to look at. The width and the choice are remembered and travel with the data folder.",
|
|
||||||
"Twenty-five satellites ship with a frequency plan instead of eleven: the eight Tevel FM cubesats, EO-88, AO-109, CAS-4A and 4B, TO-108, GreenCube's digipeater, and QO-100's wideband transponder beside its narrowband one. The list is still a starting point in a file you can correct — and the satellite picker will show every bird in the element set, plan or no plan, if you untick 'only those with a frequency plan'.",
|
|
||||||
"Satellites no longer asks for a locator you have already given. Passes are predicted from your station locator; the field is now folded away under 'the satellite antenna is at another site', which is the only case it was ever for.",
|
|
||||||
"The pass list is a proper table: headings that say what the numbers are, a satellite column as wide as the longest name instead of eight characters, a coloured dot for FM or SSB — which decides whether you reach for a handheld or the whole station — and the maximum elevation coloured by how good the pass is. A pass in progress is green, one starting within five minutes is amber. The rotator's baud rate is a dropdown like every other in OpsLog.",
|
|
||||||
"A sky plot, the way every tracker draws one: the centre is straight up, the rim is the horizon, north is at the top. The whole pass is drawn as a dashed track with arrows for the direction of travel, a hollow circle where it rises and a filled one where it sets, and a cross where the satellite is now — green above the horizon, grey below. One glance says whether the bird comes over the roof or along the treeline, which no amount of azimuth and elevation digits conveys. The radar button in the header folds it away.",
|
|
||||||
"The FT decodes table sorts on SNR, frequency, distance, country and status — click the heading. Within each slot and never across them: the periods are what the panel is, and a list sorted end to end would mix three minutes of decodes into one column with no way to tell which window any of them came from. One click sorts the way that column is worth reading (strongest signal, furthest DX, lowest frequency, A to Z, most wanted first), the second reverses it, the third gives back the order the decoder heard them in. Stations with no grid, or no country resolved yet, sort to the end either way rather than pretending to a distance of zero.",
|
"The FT decodes table sorts on SNR, frequency, distance, country and status — click the heading. Within each slot and never across them: the periods are what the panel is, and a list sorted end to end would mix three minutes of decodes into one column with no way to tell which window any of them came from. One click sorts the way that column is worth reading (strongest signal, furthest DX, lowest frequency, A to Z, most wanted first), the second reverses it, the third gives back the order the decoder heard them in. Stations with no grid, or no country resolved yet, sort to the end either way rather than pretending to a distance of zero.",
|
||||||
"The cluster editor offers a list of known nodes. Setting up a telnet cluster is the step operators get stuck on: the address and the port are two pieces of information nobody has to hand, and a typo in either looks exactly like a node that is down. Pick one and the fields fill in — F4BPO, DXFun, F5LEN, F5MZN, KM3T, SOTA, POTA, and the two Reverse Beacon feeds, which are one network on two ports where 7000 carries CW and RTTY and 7001 carries FT8 and FT4. Everything stays editable, and a node typed in by hand works exactly the same. More will be added."
|
"The cluster editor offers a list of known nodes. Setting up a telnet cluster is the step operators get stuck on: the address and the port are two pieces of information nobody has to hand, and a typo in either looks exactly like a node that is down. Pick one and the fields fill in — F4BPO, DXFun, F5LEN, F5MZN, KM3T, SOTA, POTA, and the two Reverse Beacon feeds, which are one network on two ports where 7000 carries CW and RTTY and 7001 carries FT8 and FT4. Everything stays editable, and a node typed in by hand works exactly the same. More will be added.",
|
||||||
|
"Preferences no longer lag behind the keyboard. Typing a cluster macro re-rendered the whole dialog on every keystroke and wrote a row into the database per character; the twenty-four boxes now stand on their own and the database write waits for the typing to stop."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"[NOUVEAU] Satellites. Un nouvel onglet (Outils → Satellites) suit les satellites amateurs : une carte avec l'empreinte de chacun et la trace au sol de celui qui est sélectionné, les prochains passages avec leur élévation maximale, et — pour le satellite en cours — l'azimut, l'élévation et les fréquences de descente et de montée corrigées de l'effet Doppler. Les éléments orbitaux viennent de Celestrak (avec un miroir derrière) et sont conservés sur disque : l'onglet est rempli dès son ouverture, même sans internet. Les éléments d'un satellite qu'aucun flux ne diffuse encore peuvent être collés à la main et survivent à chaque mise à jour. La liste de fréquences fournie couvre les satellites FM et linéaires ainsi que QO-100, dans un fichier que vous pouvez corriger vous-même quand un transpondeur change de mode.",
|
"[NOUVEAU] Satellites. Un nouvel onglet (Outils → Satellites) permet de travailler les satellites amateurs de bout en bout. Une carte avec l'empreinte de chacun et la trace au sol du satellite sélectionné ; une vue du ciel comme la dessine n'importe quel tracker, centre à la verticale et bord à l'horizon, avec le passage entier et la position du satellite dessus ; un compte à rebours jusqu'à l'AOS — ou jusqu'au LOS une fois levé — avec lever, culmination et coucher, leurs directions à la boussole, distance, altitude et empreinte ; et un tableau des passages de tout ce que vous suivez.\n\n« Suivre » met la radio sur le satellite et l'y maintient, chaque seconde : un IC-9700 ou IC-9100 dans son propre mode satellite, un FlexRadio sur deux slices (A la descente, B la montée, créées si elles manquent, full duplex activé) — et n'importe quel autre poste sur la descente seule, ce qu'il annonce clairement plutôt que de faire le travail à moitié. Accordez le récepteur où vous voulez : le suivi lit le VFO, y voit la station que vous avez choisie, et déplace l'émetteur en conséquence. Un rotor az/él suit aussi, piloté directement en EasyComm II ou confié à PstRotator si vous le faites déjà tourner ; un rotor 450° est utilisé comme tel, et un passage qui traverse le nord continue au lieu de se dérouler.\n\nLes QSO faits pendant le suivi sont enregistrés avec les fréquences NOMINALES, SAT_NAME, SAT_MODE et PROP_MODE=SAT — les chiffres du transpondeur, sur lesquels les deux stations peuvent s'accorder, plutôt que l'endroit où une radio se trouvait.\n\nLes éléments orbitaux viennent de Celestrak, avec un miroir derrière, et sont conservés sur disque : l'onglet est rempli dès son ouverture, même sans internet. Les éléments d'un satellite qu'aucun flux ne diffuse encore peuvent être collés à la main et survivent à chaque mise à jour. Vingt-cinq satellites sont livrés avec un plan de fréquences — les FM et les linéaires, GreenCube, QO-100 bande étroite et large — dans un fichier que vous pouvez corriger vous-même quand un transpondeur change de mode. Toute la configuration est dans Réglages → Satellites, y compris le choix des satellites suivis, sélectionnés comme on choisit ses diplômes.",
|
||||||
"Le suivi Doppler pilote la radio. « Suivre » met le poste sur le satellite et l'y maintient, chaque seconde : un IC-9700 ou IC-9100 dans son propre mode satellite, un FlexRadio sur deux slices (A la descente, B la montée, créées si elles manquent, full duplex activé) — et n'importe quelle autre radio sur la descente seule, ce qu'elle annonce clairement plutôt que de faire le travail à moitié. Accordez le récepteur où vous voulez : le suivi lit le VFO, y voit la station que vous avez choisie, et déplace l'émetteur en conséquence. Les QSO faits pendant le suivi sont enregistrés avec les fréquences NOMINALES, SAT_NAME, SAT_MODE et PROP_MODE=SAT — les chiffres du transpondeur, sur lesquels les deux stations peuvent s'accorder, plutôt que l'endroit où une radio se trouvait.",
|
|
||||||
"L'antenne suit aussi. Un rotor EasyComm II — le langage de SatPC32, Gpredict et Hamlib, donc la plupart des contrôleurs az/él — est pointé vers le satellite pendant le suivi, en série ou en réseau (Réglages → Satellites). C'est une machine distincte du rotor HF : une station qui a les deux garde les deux. Un rotor 450° est utilisé comme tel : un passage qui traverse le nord continue au-delà de 360 au lieu de se dérouler sur toute la course, antenne balayant le sol. Le panneau montre où l'antenne se trouve réellement à côté de la position du satellite — et dit clairement quand un contrôleur se contente d'accepter les commandes sans répondre, ce que beaucoup font.",
|
|
||||||
"Réglages → Satellites, c'est là qu'on configure ; l'onglet, c'est ce qu'on utilise pendant un passage. On choisit les satellites suivis comme on choisit les diplômes — deux colonnes, recherche, ceux qui ont un plan de fréquences d'abord — et l'onglet comme la liste des passages n'affichent que ceux-là. Les éléments orbitaux y passent aussi : âge, récupération, et collage des siens. Rien de tout cela ne se fait pendant qu'un satellite passe.",
|
|
||||||
"Le panneau satellite dit enfin ce qu'est un passage. Un compte à rebours jusqu'à l'AOS, ou jusqu'au LOS une fois qu'il est levé, avec une barre montrant où l'on en est ; lever, culmination et coucher avec leurs directions à la boussole ; distance, altitude et empreinte ; et s'il se rapproche ou s'éloigne, ce qui explique le sens du décalage. Les fréquences gardent la valeur corrigée en grand et le Doppler à côté.",
|
|
||||||
"Une seule liste de satellites, à un seul endroit. Réglages → Listes → Satellites disparaît : les satellites qu'on y saisissait à la main n'avaient aucun rapport avec ceux que connaissait le suivi, et une même station entretenait deux listes qui divergeaient. Le champ SAT_NAME de la saisie propose désormais les satellites que vous suivez — plus ce que l'ancienne liste contenait encore, pour ne rien perdre. Satellites quitte aussi Matériel, où il n'avait rien à faire, pour rejoindre Opération.",
|
|
||||||
"PstRotator peut aussi pointer l'antenne pour les satellites. Il gère azimut et élévation et connaît déjà votre contrôleur : si vous le faites tourner, choisissez-le dans Réglages → Satellites et OpsLog lui envoie le cap au lieu de prendre le câble lui-même — deux programmes sur un contrôleur, c'est un de trop. Le recouvrement reste son affaire : un rotor 450°, c'est à PstRotator d'en décider, pas à deux programmes en plein passage.",
|
|
||||||
"Chaque carte garde son propre fond. La carte principale et celle des carrés partageaient un seul réglage : choisir la vue satellite pour regarder les carrés repeignait aussi la carte principale, et il n'y avait aucun moyen d'avoir le relief sur l'une et les rues sur l'autre. Les quatre — principale, carrés, FT map, satellites — retiennent désormais leur propre choix, qui suit le dossier de données comme les positions mémorisées. Un choix déjà fait pour la carte des carrés est repris, pas réinitialisé.",
|
"Chaque carte garde son propre fond. La carte principale et celle des carrés partageaient un seul réglage : choisir la vue satellite pour regarder les carrés repeignait aussi la carte principale, et il n'y avait aucun moyen d'avoir le relief sur l'une et les rues sur l'autre. Les quatre — principale, carrés, FT map, satellites — retiennent désormais leur propre choix, qui suit le dossier de données comme les positions mémorisées. Un choix déjà fait pour la carte des carrés est repris, pas réinitialisé.",
|
||||||
"Deux ou trois logiciels FT8 en même temps ne se disputent plus le champ indicatif. Cliquez une station dans MSHV et lui seul a un DX Call ; WSJT-X et JTDX à côté sont au repos et le disent une fois par seconde chacun — et OpsLog y lisait MSHV abandonnant la station : le champ se vidait et se remplissait à 1 Hz, la carte zoomant au même rythme. Un DX Call effacé est désormais lu par programme, jamais à l'échelle du port ; et le logiciel qui annonce une station garde le champ jusqu'à ce qu'il efface son propre indicatif, soit fermé, ou que le QSO soit enregistré.",
|
"Deux ou trois logiciels FT8 en même temps ne se disputent plus le champ indicatif. Cliquez une station dans MSHV et lui seul a un DX Call ; WSJT-X et JTDX à côté sont au repos et le disent une fois par seconde chacun — et OpsLog y lisait MSHV abandonnant la station : le champ se vidait et se remplissait à 1 Hz, la carte zoomant au même rythme. Un DX Call effacé est désormais lu par programme, jamais à l'échelle du port ; et le logiciel qui annonce une station garde le champ jusqu'à ce qu'il efface son propre indicatif, soit fermé, ou que le QSO soit enregistré.",
|
||||||
"La colonne de droite de l'onglet Satellites se redimensionne et se masque. Tirez la poignée entre elle et la carte, double-cliquez pour la remettre par défaut, ou repliez-la avec le bouton de l'en-tête quand c'est la carte qui vous intéresse. La largeur et le choix sont mémorisés et suivent le dossier de données.",
|
|
||||||
"Vingt-cinq satellites livrés avec un plan de fréquences au lieu de onze : les huit cubesats FM Tevel, EO-88, AO-109, CAS-4A et 4B, TO-108, le digipeater de GreenCube, et le transpondeur large bande de QO-100 à côté de son bande étroite. La liste reste un point de départ, dans un fichier que vous pouvez corriger — et le sélecteur affiche tous les satellites du jeu d'éléments, avec plan ou non, si vous décochez « seulement ceux avec un plan de fréquences ».",
|
|
||||||
"Satellites ne redemande plus un locator déjà renseigné. Les passages sont calculés depuis le locator de votre station ; le champ est replié sous « l'antenne satellite est sur un autre site », le seul cas pour lequel il existait.",
|
|
||||||
"La liste des passages est un vrai tableau : des en-têtes qui disent ce que sont les chiffres, une colonne satellite à la largeur du nom le plus long au lieu de huit caractères, une pastille de couleur pour FM ou SSB — qui décide si l'on prend un portatif ou toute la station — et l'élévation maximale colorée selon la qualité du passage. Un passage en cours est vert, un passage dans moins de cinq minutes est orange. Le débit du rotor est une liste déroulante comme partout ailleurs dans OpsLog.",
|
|
||||||
"Une vue du ciel, comme la dessine n'importe quel tracker : le centre est à la verticale, le bord est l'horizon, le nord en haut. Le passage entier est tracé en pointillés avec des flèches donnant le sens de déplacement, un cercle vide au lever et un cercle plein au coucher, et une croix là où se trouve le satellite — verte au-dessus de l'horizon, grise en dessous. Un coup d'œil dit si le satellite passe au-dessus du toit ou le long des arbres, ce qu'aucune succession de chiffres d'azimut et d'élévation ne transmet. Le bouton radar de l'en-tête la replie.",
|
|
||||||
"Le tableau des décodages FT se trie sur SNR, fréquence, distance, pays et statut — cliquez l'en-tête. À l'intérieur de chaque créneau et jamais au travers : les périodes sont la raison d'être du panneau, et un tri de bout en bout mélangerait trois minutes de décodages en une colonne sans plus savoir de quelle fenêtre chacun vient. Un clic trie dans le sens où la colonne se lit (signal le plus fort, DX le plus lointain, fréquence la plus basse, de A à Z, le plus recherché d'abord), un second inverse, un troisième rend l'ordre dans lequel le décodeur les a entendus. Les stations sans locator, ou dont le pays n'est pas encore résolu, se rangent à la fin dans les deux sens plutôt que de se faire passer pour une distance nulle.",
|
"Le tableau des décodages FT se trie sur SNR, fréquence, distance, pays et statut — cliquez l'en-tête. À l'intérieur de chaque créneau et jamais au travers : les périodes sont la raison d'être du panneau, et un tri de bout en bout mélangerait trois minutes de décodages en une colonne sans plus savoir de quelle fenêtre chacun vient. Un clic trie dans le sens où la colonne se lit (signal le plus fort, DX le plus lointain, fréquence la plus basse, de A à Z, le plus recherché d'abord), un second inverse, un troisième rend l'ordre dans lequel le décodeur les a entendus. Les stations sans locator, ou dont le pays n'est pas encore résolu, se rangent à la fin dans les deux sens plutôt que de se faire passer pour une distance nulle.",
|
||||||
"L'éditeur de cluster propose une liste de nœuds connus. La configuration d'un cluster telnet est l'étape où l'on se bloque : l'adresse et le port sont deux informations que personne n'a sous la main, et une faute de frappe dans l'une ou l'autre ressemble exactement à un nœud en panne. On en choisit un et les champs se remplissent — F4BPO, DXFun, F5LEN, F5MZN, KM3T, SOTA, POTA, et les deux flux Reverse Beacon, qui sont un même réseau sur deux ports où 7000 porte la CW et le RTTY et 7001 le FT8 et le FT4. Tout reste modifiable, et un nœud saisi à la main fonctionne exactement pareil. D'autres seront ajoutés."
|
"L'éditeur de cluster propose une liste de nœuds connus. La configuration d'un cluster telnet est l'étape où l'on se bloque : l'adresse et le port sont deux informations que personne n'a sous la main, et une faute de frappe dans l'une ou l'autre ressemble exactement à un nœud en panne. On en choisit un et les champs se remplissent — F4BPO, DXFun, F5LEN, F5MZN, KM3T, SOTA, POTA, et les deux flux Reverse Beacon, qui sont un même réseau sur deux ports où 7000 porte la CW et le RTTY et 7001 le FT8 et le FT4. Tout reste modifiable, et un nœud saisi à la main fonctionne exactement pareil. D'autres seront ajoutés.",
|
||||||
|
"Les Préférences ne traînent plus derrière le clavier. Saisir une macro de cluster redessinait tout le dialogue à chaque frappe et écrivait une ligne en base par caractère ; les vingt-quatre champs sont désormais indépendants et l'écriture en base attend la fin de la saisie."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -916,6 +916,66 @@ const MOTOR_BAND_DEFAULT_KHZ: Record<string, number> = {
|
|||||||
const relayCountUI = (type: string) =>
|
const relayCountUI = (type: string) =>
|
||||||
type === 'kmtronic' || type === 'denkovi' ? 8 : type === 'httpgen' ? 4 : 5;
|
type === 'kmtronic' || type === 'denkovi' ? 8 : type === 'httpgen' ? 4 : 5;
|
||||||
|
|
||||||
|
// The twelve cluster command macros.
|
||||||
|
//
|
||||||
|
// Module-scoped, with its own state, and that is the point rather than tidiness:
|
||||||
|
// nested inside SettingsModal every keystroke in one of these twenty-four boxes
|
||||||
|
// re-rendered the WHOLE preferences dialog — every list, every form, every
|
||||||
|
// panel — and the letters arrived visibly after the finger had left the key.
|
||||||
|
// Here a keystroke re-renders twelve rows.
|
||||||
|
//
|
||||||
|
// Written on every keystroke as before. This panel has no Save button, and a
|
||||||
|
// text box whose contents only take effect on some other button is how work
|
||||||
|
// gets lost; the database write is what waits (see saveClusterMacros).
|
||||||
|
function ClusterMacroEditor() {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const [macros, setMacros] = useState<ClusterMacro[]>(loadClusterMacros);
|
||||||
|
const setMacro = (i: number, patch: Partial<ClusterMacro>) => {
|
||||||
|
setMacros((cur) => {
|
||||||
|
const next = cur.map((m, j) => (j === i ? { ...m, ...patch } : m));
|
||||||
|
saveClusterMacros(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="border-t border-border/60 pt-3 space-y-2">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium">{t('clu.macros')}</span>
|
||||||
|
</div>
|
||||||
|
{/* Two columns of six: twelve rows stacked would push everything else
|
||||||
|
in this panel off the screen. */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1.5">
|
||||||
|
{macros.map((m, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-1.5">
|
||||||
|
<span className="text-[10px] text-muted-foreground tabular-nums w-4 text-right shrink-0">{i + 1}</span>
|
||||||
|
<Input
|
||||||
|
className="h-8 w-28 shrink-0 text-xs"
|
||||||
|
placeholder={t('clu.macroLabel')}
|
||||||
|
value={m.label}
|
||||||
|
maxLength={24}
|
||||||
|
onChange={(e) => setMacro(i, { label: e.target.value })}
|
||||||
|
/>
|
||||||
|
{/* 500, not 120. A DXSpider filter is a list of prefixes and an
|
||||||
|
operator's own list of wanted countries runs past a hundred
|
||||||
|
characters easily — the field simply stopped accepting
|
||||||
|
keystrokes, with nothing to say why, and the command was saved
|
||||||
|
truncated. The title shows the whole thing, since the box
|
||||||
|
cannot. */}
|
||||||
|
<Input
|
||||||
|
className="h-8 flex-1 min-w-0 font-mono text-xs"
|
||||||
|
placeholder={t('clu.macroCmd')}
|
||||||
|
value={m.cmd}
|
||||||
|
title={m.cmd}
|
||||||
|
maxLength={500}
|
||||||
|
onChange={(e) => setMacro(i, { cmd: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Live status + OPERATE/STANDBY toggle for ONE configured amplifier (by config
|
// Live status + OPERATE/STANDBY toggle for ONE configured amplifier (by config
|
||||||
// id) — SPE / ACOM / PGXL alike. Module-scoped (not a nested component) so it
|
// id) — SPE / ACOM / PGXL alike. Module-scoped (not a nested component) so it
|
||||||
// isn't remounted on every parent render. Polls once a second while shown.
|
// isn't remounted on every parent render. Polls once a second while shown.
|
||||||
@@ -1950,9 +2010,6 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
|||||||
const [milesUnit, setMilesUnit] = useState(() => localStorage.getItem('opslog.distanceMiles') === '1');
|
const [milesUnit, setMilesUnit] = useState(() => localStorage.getItem('opslog.distanceMiles') === '1');
|
||||||
const [region, setRegion] = useState<IaruRegion>(() => iaruRegion());
|
const [region, setRegion] = useState<IaruRegion>(() => iaruRegion());
|
||||||
const [clusterWorkedSameSlot, setClusterWorkedSameSlot] = useState(() => localStorage.getItem('opslog.clusterWorkedSameSlot') === '1');
|
const [clusterWorkedSameSlot, setClusterWorkedSameSlot] = useState(() => localStorage.getItem('opslog.clusterWorkedSameSlot') === '1');
|
||||||
// Declared HERE and not in ClusterPanel: that renderer is called as a plain
|
|
||||||
// function by the PANELS map, so it must stay hooks-free.
|
|
||||||
const [clusterMacros, setClusterMacros] = useState<ClusterMacro[]>(loadClusterMacros);
|
|
||||||
const [showQsoRate, setShowQsoRate] = useState(() => localStorage.getItem('opslog.showQsoRate') === '1');
|
const [showQsoRate, setShowQsoRate] = useState(() => localStorage.getItem('opslog.showQsoRate') === '1');
|
||||||
const [catModeBeforeFreq, setCatModeBeforeFreq] = useState(() => localStorage.getItem('opslog.catModeBeforeFreq') === '1');
|
const [catModeBeforeFreq, setCatModeBeforeFreq] = useState(() => localStorage.getItem('opslog.catModeBeforeFreq') === '1');
|
||||||
// Password-encryption (secret vault) state.
|
// Password-encryption (secret vault) state.
|
||||||
@@ -5975,14 +6032,6 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
|||||||
|
|
||||||
function ClusterPanel() {
|
function ClusterPanel() {
|
||||||
const sorted = [...clusterServers].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
|
const sorted = [...clusterServers].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
|
||||||
// Written on every keystroke. This panel has no Save button, and a pair of
|
|
||||||
// text boxes whose contents only take effect on some other button is how
|
|
||||||
// work gets lost.
|
|
||||||
const setMacro = (i: number, patch: Partial<ClusterMacro>) => {
|
|
||||||
const next = clusterMacros.map((m, j) => (j === i ? { ...m, ...patch } : m));
|
|
||||||
setClusterMacros(next);
|
|
||||||
saveClusterMacros(next);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
@@ -6075,41 +6124,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
|||||||
{t('clu.autoConnect')}
|
{t('clu.autoConnect')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-border/60 pt-3 space-y-2">
|
<ClusterMacroEditor />
|
||||||
<div>
|
|
||||||
<span className="text-sm font-medium">{t('clu.macros')}</span>
|
|
||||||
</div>
|
|
||||||
{/* Two columns of six: twelve rows stacked would push everything else
|
|
||||||
in this panel off the screen. */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1.5">
|
|
||||||
{clusterMacros.map((m, i) => (
|
|
||||||
<div key={i} className="flex items-center gap-1.5">
|
|
||||||
<span className="text-[10px] text-muted-foreground tabular-nums w-4 text-right shrink-0">{i + 1}</span>
|
|
||||||
<Input
|
|
||||||
className="h-8 w-28 shrink-0 text-xs"
|
|
||||||
placeholder={t('clu.macroLabel')}
|
|
||||||
value={m.label}
|
|
||||||
maxLength={24}
|
|
||||||
onChange={(e) => setMacro(i, { label: e.target.value })}
|
|
||||||
/>
|
|
||||||
{/* 500, not 120. A DXSpider filter is a list of prefixes and
|
|
||||||
an operator's own list of wanted countries runs past a
|
|
||||||
hundred characters easily — the field simply stopped
|
|
||||||
accepting keystrokes, with nothing to say why, and the
|
|
||||||
command was saved truncated. The title shows the whole
|
|
||||||
thing, since the box cannot. */}
|
|
||||||
<Input
|
|
||||||
className="h-8 flex-1 min-w-0 font-mono text-xs"
|
|
||||||
placeholder={t('clu.macroCmd')}
|
|
||||||
value={m.cmd}
|
|
||||||
title={m.cmd}
|
|
||||||
maxLength={500}
|
|
||||||
onChange={(e) => setMacro(i, { cmd: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label className="flex items-start gap-2 text-sm cursor-pointer border-t border-border/60 pt-3">
|
<label className="flex items-start gap-2 text-sm cursor-pointer border-t border-border/60 pt-3">
|
||||||
<Checkbox checked={clusterWorkedSameSlot} className="mt-0.5"
|
<Checkbox checked={clusterWorkedSameSlot} className="mt-0.5"
|
||||||
onCheckedChange={(c) => { const v = !!c; setClusterWorkedSameSlot(v); writeUiPref('opslog.clusterWorkedSameSlot', v ? '1' : '0'); }} />
|
onCheckedChange={(c) => { const v = !!c; setClusterWorkedSameSlot(v); writeUiPref('opslog.clusterWorkedSameSlot', v ? '1' : '0'); }} />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
// Stored through writeUiPref like every other portable preference, so the
|
// Stored through writeUiPref like every other portable preference, so the
|
||||||
// buttons travel with data/ rather than living in one browser profile.
|
// buttons travel with data/ rather than living in one browser profile.
|
||||||
|
|
||||||
import { writeUiPref } from '@/lib/uiPref';
|
import { writeUiPrefDebounced } from '@/lib/uiPref';
|
||||||
|
|
||||||
export type ClusterMacro = {
|
export type ClusterMacro = {
|
||||||
label: string; // what the button says
|
label: string; // what the button says
|
||||||
@@ -43,8 +43,12 @@ export function loadClusterMacros(): ClusterMacro[] {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debounced, because this is called on every keystroke in twenty-four text
|
||||||
|
// boxes. The local cache is written at once — it is what everything reads back
|
||||||
|
// — and only the database write waits for the typing to stop. A round trip into
|
||||||
|
// Go per character is what "the letters appear after I have moved on" was.
|
||||||
export function saveClusterMacros(macros: ClusterMacro[]): void {
|
export function saveClusterMacros(macros: ClusterMacro[]): void {
|
||||||
writeUiPref(clusterMacrosKey, JSON.stringify(macros));
|
writeUiPrefDebounced(clusterMacrosKey, JSON.stringify(macros));
|
||||||
}
|
}
|
||||||
|
|
||||||
// visibleClusterMacros drops the slots that would send nothing. The COMMAND is
|
// visibleClusterMacros drops the slots that would send nothing. The COMMAND is
|
||||||
|
|||||||
@@ -93,6 +93,50 @@ export async function syncPortablePrefs(): Promise<void> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeUiPrefDebounced is writeUiPref for a value that changes AS SOMEBODY
|
||||||
|
// TYPES.
|
||||||
|
//
|
||||||
|
// The local cache is written at once, because that is what the interface reads
|
||||||
|
// back and it costs nothing. The DATABASE write is held until the typing stops:
|
||||||
|
// writeUiPref crosses into Go and writes a row, and doing that per character in
|
||||||
|
// a text box is a round trip per keystroke — twenty-four boxes of cluster
|
||||||
|
// macros was exactly that, and it showed as characters appearing after the
|
||||||
|
// finger had left the key.
|
||||||
|
//
|
||||||
|
// Pending writes are flushed when the page goes away, so a value typed and
|
||||||
|
// immediately followed by a close is not lost.
|
||||||
|
const pendingPrefs = new Map<string, { value: string; timer: number }>();
|
||||||
|
|
||||||
|
export function writeUiPrefDebounced(key: string, value: string, ms = 400): void {
|
||||||
|
try { localStorage.setItem(key, value); } catch { /* quota / private mode */ }
|
||||||
|
const prev = pendingPrefs.get(key);
|
||||||
|
if (prev) window.clearTimeout(prev.timer);
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
pendingPrefs.delete(key);
|
||||||
|
SetUIPref(key, value).catch((e: any) => {
|
||||||
|
try { LogUIError('ui pref', 'could not store ' + key + ': ' + String(e?.message ?? e), ''); } catch { /* nothing left to try */ }
|
||||||
|
});
|
||||||
|
}, ms);
|
||||||
|
pendingPrefs.set(key, { value, timer });
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushUiPrefs writes every pending value immediately.
|
||||||
|
export function flushUiPrefs(): void {
|
||||||
|
for (const [key, p] of pendingPrefs) {
|
||||||
|
window.clearTimeout(p.timer);
|
||||||
|
SetUIPref(key, p.value).catch(() => { /* the local cache still holds it */ });
|
||||||
|
}
|
||||||
|
pendingPrefs.clear();
|
||||||
|
}
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('beforeunload', flushUiPrefs);
|
||||||
|
// Closing the app does not always fire beforeunload in a WebView; a hidden
|
||||||
|
// page is the earlier and more reliable signal.
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'hidden') flushUiPrefs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// writeUiPref write-throughs a value to the local cache AND the portable DB.
|
// writeUiPref write-throughs a value to the local cache AND the portable DB.
|
||||||
// Use it everywhere these keys are written instead of localStorage.setItem.
|
// Use it everywhere these keys are written instead of localStorage.setItem.
|
||||||
export function writeUiPref(key: string, value: string): void {
|
export function writeUiPref(key: string, value: string): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user