@rizal72/oc-models-pro 0.1.5 → 0.3.0
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/README.md +31 -20
- package/aa.ts +129 -0
- package/bench.ts +8 -4
- package/dist/gui-experimental.cjs +58 -0
- package/index.ts +143 -2
- package/multipliers.ts +9 -0
- package/package.json +9 -5
- package/prefs.ts +92 -0
- package/rows.ts +301 -0
- package/rpc.ts +108 -0
- package/tps.ts +212 -0
- package/tui.js +117 -200
- package/vite.config.ts +6 -0
package/tui.js
CHANGED
|
@@ -18,137 +18,24 @@ import { createElement as _$createElement } from "@opentui/solid";
|
|
|
18
18
|
// is the first row whatever it costs: the free/paying split never buries it.
|
|
19
19
|
import { Plugin } from "@opencode/plugin/tui";
|
|
20
20
|
import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
|
21
|
-
import { appendFileSync, readFileSync,
|
|
21
|
+
import { appendFileSync, readFileSync, watch, writeFileSync } from "node:fs";
|
|
22
22
|
import { homedir, tmpdir } from "node:os";
|
|
23
23
|
import { join } from "node:path";
|
|
24
|
-
import { benchFor, benchIndex } from "./bench.ts";
|
|
25
|
-
import {
|
|
24
|
+
import { benchFor, benchIndex, benchKey } from "./bench.ts";
|
|
25
|
+
import { aaIndex } from "./aa.ts";
|
|
26
|
+
import { speedIndex } from "./tps.ts";
|
|
27
|
+
import { credits, goPlan, key, nativePeak } from "./multipliers.ts";
|
|
26
28
|
import { LEGEND_MIN, fit, hintLines, rowWidth } from "./fit.ts";
|
|
27
29
|
import { tagsFor } from "./tags.ts";
|
|
30
|
+
import { prefKey, readFavourites, readPrefs, toggleFavorite, writeFavourites, PREFS_FILE } from "./prefs.ts";
|
|
31
|
+
import { ORDER, ORDERS, arrange, compare, isFree } from "./rows.ts";
|
|
28
32
|
const SCOPES = ["all", "favorites", "paying"];
|
|
29
|
-
const prefKey = (providerID, modelID) => providerID + "/" + modelID;
|
|
30
|
-
|
|
31
|
-
/** The picker's own state file: `favorite[]` and `recent[]` are `{ providerID, modelID }`. */
|
|
32
|
-
|
|
33
|
-
const PREFS_FILE = join(homedir(), ".local/state/opencode/model.json");
|
|
34
|
-
/** Same directory, a leading dot: the host watches `model.json.*`, not this. */
|
|
35
|
-
const TEMP_FILE = join(homedir(), ".local/state/opencode", ".model.json.tmp");
|
|
36
33
|
let prefs = {
|
|
37
34
|
favorite: new Set(),
|
|
38
35
|
recent: new Map(),
|
|
39
36
|
variants: new Map()
|
|
40
37
|
};
|
|
41
38
|
|
|
42
|
-
/** The host's `favorite[]` as it is in the file: a key cannot be split back — a model id may hold a slash. */
|
|
43
|
-
function readFavourites() {
|
|
44
|
-
try {
|
|
45
|
-
const data = JSON.parse(readFileSync(PREFS_FILE, "utf8"));
|
|
46
|
-
if (!Array.isArray(data.favorite)) return [];
|
|
47
|
-
return data.favorite.filter(entry => typeof entry?.providerID === "string" && typeof entry?.modelID === "string");
|
|
48
|
-
} catch (error) {
|
|
49
|
-
// A missing file is not an error: starting empty creates it on the write. Anything else is.
|
|
50
|
-
if (error?.code === "ENOENT") return [];
|
|
51
|
-
trace("favourites: " + (error instanceof Error ? error.message : String(error)));
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Rewrites `favorite[]` with the rest of the file untouched. The host watches this path and
|
|
57
|
-
* `model.json.*`, so the atomic rename is seen and the native picker, `model.cycle_favorite` and this
|
|
58
|
-
* list stay in step; a dot in front keeps the temp name outside that glob. */
|
|
59
|
-
function writeFavourites(list) {
|
|
60
|
-
try {
|
|
61
|
-
let raw;
|
|
62
|
-
try {
|
|
63
|
-
raw = JSON.parse(readFileSync(PREFS_FILE, "utf8"));
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (error?.code !== "ENOENT") throw error;
|
|
66
|
-
raw = {};
|
|
67
|
-
}
|
|
68
|
-
raw.favorite = list;
|
|
69
|
-
writeFileSync(TEMP_FILE, JSON.stringify(raw));
|
|
70
|
-
renameSync(TEMP_FILE, PREFS_FILE);
|
|
71
|
-
return true;
|
|
72
|
-
} catch (error) {
|
|
73
|
-
trace("favourites: " + (error instanceof Error ? error.message : String(error)));
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function toggleFavorite(providerID, modelID) {
|
|
78
|
-
const key = prefKey(providerID, modelID);
|
|
79
|
-
const list = readFavourites();
|
|
80
|
-
if (!list) return undefined;
|
|
81
|
-
const kept = list.filter(entry => prefKey(entry.providerID, entry.modelID) !== key);
|
|
82
|
-
const on = kept.length === list.length;
|
|
83
|
-
return writeFavourites(on ? [...kept, {
|
|
84
|
-
providerID,
|
|
85
|
-
modelID
|
|
86
|
-
}] : kept) ? on : undefined;
|
|
87
|
-
}
|
|
88
|
-
function readPrefs() {
|
|
89
|
-
const parsed = {
|
|
90
|
-
favorite: new Set(),
|
|
91
|
-
recent: new Map(),
|
|
92
|
-
variants: new Map()
|
|
93
|
-
};
|
|
94
|
-
try {
|
|
95
|
-
const data = JSON.parse(readFileSync(PREFS_FILE, "utf8"));
|
|
96
|
-
for (const entry of data.favorite ?? []) parsed.favorite.add(prefKey(entry.providerID, entry.modelID));
|
|
97
|
-
for (const [index, entry] of (data.recent ?? []).entries()) parsed.recent.set(prefKey(entry.providerID, entry.modelID), index);
|
|
98
|
-
for (const [model, variant] of Object.entries(data.variant ?? {})) if (typeof variant === "string") parsed.variants.set(model, variant);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
// A corrupt state file empties the favourites and the recent list: say so instead of pretending.
|
|
101
|
-
trace("prefs unreadable: " + (error instanceof Error ? error.message : String(error)));
|
|
102
|
-
}
|
|
103
|
-
return parsed;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** What the Go plan makes a model cost: the figure the effective order sorts by. An unknown price is
|
|
107
|
-
* not zero — it sorts last, or it would sit at the top of the cheapest list forever. */
|
|
108
|
-
function effective(row) {
|
|
109
|
-
if (row.input === undefined) return Number.POSITIVE_INFINITY;
|
|
110
|
-
return row.input / (row.credit || 1);
|
|
111
|
-
}
|
|
112
|
-
const SORTS = [{
|
|
113
|
-
label: "effective price",
|
|
114
|
-
short: "effective↑",
|
|
115
|
-
value: effective
|
|
116
|
-
}, {
|
|
117
|
-
label: "input price",
|
|
118
|
-
short: "in↑",
|
|
119
|
-
value: row => row.input ?? Number.POSITIVE_INFINITY
|
|
120
|
-
}, {
|
|
121
|
-
label: "output price",
|
|
122
|
-
short: "out↑",
|
|
123
|
-
value: row => row.output ?? Number.POSITIVE_INFINITY
|
|
124
|
-
}, {
|
|
125
|
-
label: "input + output",
|
|
126
|
-
short: "in+out↑",
|
|
127
|
-
value: row => row.input === undefined || row.output === undefined ? Number.POSITIVE_INFINITY : (row.input + row.output) / (row.credit || 1)
|
|
128
|
-
}, {
|
|
129
|
-
label: "context window",
|
|
130
|
-
short: "ctx↓",
|
|
131
|
-
value: row => row.context === undefined ? Number.POSITIVE_INFINITY : -row.context
|
|
132
|
-
},
|
|
133
|
-
// A missing score sorts last, never first: Pi's rule, and the honest one.
|
|
134
|
-
{
|
|
135
|
-
label: "intelligence",
|
|
136
|
-
short: "int↓",
|
|
137
|
-
value: row => -(row.int ?? -1)
|
|
138
|
-
}, {
|
|
139
|
-
label: "speed",
|
|
140
|
-
short: "speed↓",
|
|
141
|
-
value: row => -(row.tps ?? -1)
|
|
142
|
-
}, {
|
|
143
|
-
label: "recently used",
|
|
144
|
-
short: "recent",
|
|
145
|
-
value: row => prefs.recent.get(prefKey(row.providerID, row.modelID)) ?? 1e9
|
|
146
|
-
}, {
|
|
147
|
-
label: "name",
|
|
148
|
-
short: "name",
|
|
149
|
-
value: row => 0
|
|
150
|
-
}];
|
|
151
|
-
|
|
152
39
|
/** Fallbacks, used only when the theme defines none of the candidates. */
|
|
153
40
|
const FALLBACK_DIM = "#8a8a8a";
|
|
154
41
|
const FALLBACK_ACCENT = "#5a9fd4";
|
|
@@ -191,17 +78,39 @@ function price(value, width) {
|
|
|
191
78
|
function isGo(providerID) {
|
|
192
79
|
return providerID === "opencode-go";
|
|
193
80
|
}
|
|
194
|
-
function free(row) {
|
|
195
|
-
return row.input === 0 && row.output === 0;
|
|
196
|
-
}
|
|
197
81
|
function headerRow(f) {
|
|
198
82
|
return [" ", " ", "model".padEnd(f.name), cell("in", 6), cell("out", 6), ...(f.ctx ? [cell("ctx", 5)] : []), ...(f.int ? [cell("int", 5)] : []), ...(f.tkps ? [cell("tok/s", 6)] : []), cell("cred", 4), ...(f.note ? [cell("note", 6)] : [])].join(" ");
|
|
199
83
|
}
|
|
200
|
-
|
|
84
|
+
|
|
85
|
+
/** The speed cell on its own, so the renderer can colour it apart from the rest of the row. */
|
|
86
|
+
function speedCell(row, f) {
|
|
87
|
+
if (!f.tkps) return "";
|
|
88
|
+
const text = row.measured !== undefined ? String(Math.round(row.measured)) : row.tps !== undefined ? "~" + Math.round(row.tps) : "";
|
|
89
|
+
return cell(text, 6);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A row in three pieces: everything before the speed, the speed, everything after. */
|
|
93
|
+
function rowParts(row, mine, favourite, f) {
|
|
201
94
|
const notes = [row.peak ? "peak*" + row.peak : "", row.promo ? "+" + row.promo + "x" : ""].filter(Boolean);
|
|
202
|
-
|
|
95
|
+
const head = [(mine ? "●" : " ") + (favourite ? "*" : " "), padTag(row.tag), label(row.name, f.name), price(row.input, 6), price(row.output, 6), ...(f.ctx ? [cell(tokens(row.context), 5)] : []), ...(f.int ? [cell(row.int !== undefined ? row.int.toFixed(1) : "", 5)] : [])];
|
|
96
|
+
const tail = [
|
|
203
97
|
// Any credit other than 1 changes the effective price, so any of them is shown.
|
|
204
|
-
cell(row.credit !== 1 ? "×" + row.credit : "", 4), ...(f.note ? [cell(notes.join(" "), 6)] : [])]
|
|
98
|
+
cell(row.credit !== 1 ? "×" + row.credit : "", 4), ...(f.note ? [cell(notes.join(" "), 6)] : [])];
|
|
99
|
+
const speed = speedCell(row, f);
|
|
100
|
+
if (!speed) return {
|
|
101
|
+
before: head.concat(tail).join(" "),
|
|
102
|
+
speed: "",
|
|
103
|
+
after: ""
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
before: head.join(" ") + " ",
|
|
107
|
+
speed,
|
|
108
|
+
after: tail.length ? " " + tail.join(" ") : ""
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function modelRow(row, mine, favourite, f) {
|
|
112
|
+
const parts = rowParts(row, mine, favourite, f);
|
|
113
|
+
return parts.before + parts.speed + parts.after;
|
|
205
114
|
}
|
|
206
115
|
const TRACE = join(tmpdir(), "oc-models-pro.log");
|
|
207
116
|
|
|
@@ -254,11 +163,15 @@ export default Plugin.define({
|
|
|
254
163
|
prefs = readPrefs();
|
|
255
164
|
let creditIndex = new Map();
|
|
256
165
|
let bench = new Map();
|
|
166
|
+
/** Measured on this machine, keyed `providerID/modelID`: it wins over any catalogue figure. */
|
|
167
|
+
let measured = new Map();
|
|
168
|
+
/** From Artificial Analysis, for the models this machine never ran. */
|
|
169
|
+
let estimated = new Map();
|
|
257
170
|
/** False when the Go page could not be read and no cache was there: the credits are all 1. */
|
|
258
171
|
let creditsKnown = false;
|
|
259
172
|
// Signals, not plain variables: the header shows them, and a plain variable left the title
|
|
260
173
|
// saying "effective price" whatever the list was actually sorted by.
|
|
261
|
-
const [sort, setSort] = createSignal(
|
|
174
|
+
const [sort, setSort] = createSignal("effective");
|
|
262
175
|
const [grouped, setGrouped] = createSignal(true);
|
|
263
176
|
const [scope, setScope] = createSignal("all");
|
|
264
177
|
function fail(label, error) {
|
|
@@ -296,6 +209,10 @@ export default Plugin.define({
|
|
|
296
209
|
const cost = (model.cost ?? []).find(entry => !entry.tier) ?? model.cost?.[0];
|
|
297
210
|
const limit = model.limit ?? {};
|
|
298
211
|
const score = benchFor(bench, model.name, model.modelID);
|
|
212
|
+
// Three sources, one order: what this machine measured, then Artificial Analysis, then
|
|
213
|
+
// the catalogue. A measured figure is never displaced by an estimate.
|
|
214
|
+
const mine = measured.get(model.providerID + "/" + model.modelID);
|
|
215
|
+
const theirs = mine === undefined ? estimated.get(benchKey(model.name)) ?? estimated.get(benchKey(model.modelID)) ?? score?.tps : undefined;
|
|
299
216
|
result.push({
|
|
300
217
|
name: model.name,
|
|
301
218
|
providerID: model.providerID,
|
|
@@ -314,9 +231,12 @@ export default Plugin.define({
|
|
|
314
231
|
// "DeepSeek V4.1 Flash" inherited the Go credit.
|
|
315
232
|
credit: isGo(model.providerID) ? creditIndex.get(key(model.modelID))?.credit ?? 1 : 1,
|
|
316
233
|
promo: isGo(model.providerID) ? creditIndex.get(key(model.modelID))?.promo : undefined,
|
|
317
|
-
|
|
234
|
+
// Measured on the Go plan first, then the native rule: the catalogue lists one
|
|
235
|
+
// DeepSeek price, so only the second can say it doubles.
|
|
236
|
+
peak: (isGo(model.providerID) ? creditIndex.get(key(model.modelID))?.peak : undefined) ?? nativePeak(model.providerID),
|
|
318
237
|
int: score?.int,
|
|
319
|
-
tps:
|
|
238
|
+
tps: theirs,
|
|
239
|
+
measured: mine,
|
|
320
240
|
enabled: model.enabled !== false
|
|
321
241
|
});
|
|
322
242
|
}
|
|
@@ -340,7 +260,21 @@ export default Plugin.define({
|
|
|
340
260
|
// One fetch a day at most; a failure only costs the credit column and the effective ordering.
|
|
341
261
|
const plan = await goPlan();
|
|
342
262
|
creditIndex = plan ? credits(plan) : new Map();
|
|
343
|
-
|
|
263
|
+
// The three reads run together: the panel waits for the slowest, not for their sum. Measured
|
|
264
|
+
// comes first in the ordering below — the only figure that is this provider's own behaviour.
|
|
265
|
+
const started = Date.now();
|
|
266
|
+
const [benchmarks, speeds, estimates] = await Promise.all([benchIndex(), speedIndex(), aaIndex()]);
|
|
267
|
+
bench = benchmarks;
|
|
268
|
+
measured = new Map([...speeds.speeds].map(([key, speed]) => [key, speed.tps]));
|
|
269
|
+
estimated = estimates;
|
|
270
|
+
// How many of the models on offer actually come out with a figure: the one number that says
|
|
271
|
+
// whether a thin column is a thin source or a broken key.
|
|
272
|
+
const offer = listModels();
|
|
273
|
+
const withSpeed = offer.filter(model => {
|
|
274
|
+
if (measured.has(model.providerID + "/" + model.modelID)) return true;
|
|
275
|
+
return estimated.get(benchKey(model.name)) !== undefined || estimated.get(benchKey(model.modelID)) !== undefined;
|
|
276
|
+
}).length;
|
|
277
|
+
trace("open: tps=" + speeds.turns + " turns, " + measured.size + " measured, " + estimated.size + " from AA, " + withSpeed + "/" + offer.length + " with a speed, " + (Date.now() - started) + " ms");
|
|
344
278
|
creditsKnown = creditIndex.size > 0;
|
|
345
279
|
trace("open: models=" + listModels().length + " bench=" + bench.size + " plan=" + (plan ? "$" + plan.price + " " + plan.limits.length + " rows" : "none") + " credits=" + creditIndex.size + " prefs=" + prefs.favorite.size + "/" + prefs.recent.size);
|
|
346
280
|
if (!ui.panel.open("oc-models-pro")) {
|
|
@@ -452,66 +386,35 @@ export default Plugin.define({
|
|
|
452
386
|
const needle = query().trim().toLowerCase();
|
|
453
387
|
let list = rows().filter(row => row.enabled);
|
|
454
388
|
if (scope() === "favorites") list = list.filter(row => prefs.favorite.has(prefKey(row.providerID, row.modelID)) || prefKey(row.providerID, row.modelID) === mine);
|
|
455
|
-
if (scope() === "paying") list = list.filter(row => !
|
|
389
|
+
if (scope() === "paying") list = list.filter(row => !isFree(row) || prefKey(row.providerID, row.modelID) === mine);
|
|
456
390
|
if (needle) list = list.filter(row => (row.name + " " + row.provider + " " + row.providerID).toLowerCase().includes(needle));
|
|
457
391
|
list = [...list].sort((a, b) => {
|
|
458
392
|
const mineA = prefKey(a.providerID, a.modelID) === mine;
|
|
459
393
|
const mineB = prefKey(b.providerID, b.modelID) === mine;
|
|
460
394
|
if (mineA !== mineB) return mineA ? -1 : 1;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
return a.name.localeCompare(b.name);
|
|
395
|
+
return compare(sort(), a, b, {
|
|
396
|
+
recent: prefs.recent
|
|
397
|
+
});
|
|
466
398
|
});
|
|
467
399
|
|
|
468
400
|
// Free models never enter the price order: they are not cheap, they are unmetered, and at
|
|
469
401
|
// the top they hide everything that costs something. Grouped, each one still sits under
|
|
470
402
|
// its own provider, after that provider's paying rows — the group answers "who serves
|
|
471
403
|
// this", and a global "Free" bucket would answer it wrong. Flat, they keep one bucket.
|
|
404
|
+
// What is drawn, and in which order, is `arrange`'s business — the same function the
|
|
405
|
+
// desktop panel calls. This half only turns sections into the headers and rows a
|
|
406
|
+
// terminal list wants.
|
|
472
407
|
const items = [];
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
// Two bits: the model in use first, then the active order inside each of the two classes.
|
|
481
|
-
// It leads its group even when it is free — it is the state of the session, not a price.
|
|
482
|
-
for (const bucket of groups.values()) bucket.sort((a, b) => {
|
|
483
|
-
const mineA = prefKey(a.providerID, a.modelID) === mine;
|
|
484
|
-
const mineB = prefKey(b.providerID, b.modelID) === mine;
|
|
485
|
-
if (mineA !== mineB) return mineA ? -1 : 1;
|
|
486
|
-
return Number(free(a)) - Number(free(b));
|
|
408
|
+
for (const section of arrange(list, {
|
|
409
|
+
inUse: mine,
|
|
410
|
+
grouped: grouped()
|
|
411
|
+
})) {
|
|
412
|
+
if (section.title) items.push({
|
|
413
|
+
header: section.title
|
|
487
414
|
});
|
|
488
|
-
const
|
|
489
|
-
// The provider of the model in use comes first, like the native picker.
|
|
490
|
-
const mineProvider = list.find(row => prefKey(row.providerID, row.modelID) === mine)?.provider;
|
|
491
|
-
if (mineProvider) names.sort((a, b) => a === mineProvider ? -1 : b === mineProvider ? 1 : 0);
|
|
492
|
-
for (const name of names) {
|
|
493
|
-
items.push({
|
|
494
|
-
header: name
|
|
495
|
-
});
|
|
496
|
-
for (const row of groups.get(name)) items.push({
|
|
497
|
-
row
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
} else {
|
|
501
|
-
// The model in use leads the list, free or not: it is the state of the session, not a
|
|
502
|
-
// price to compare. The other free models keep the bucket at the end.
|
|
503
|
-
for (const row of list) if (!free(row) || prefKey(row.providerID, row.modelID) === mine) items.push({
|
|
415
|
+
for (const row of section.rows) items.push({
|
|
504
416
|
row
|
|
505
417
|
});
|
|
506
|
-
const freeRows = list.filter(row => free(row) && prefKey(row.providerID, row.modelID) !== mine);
|
|
507
|
-
if (freeRows.length) {
|
|
508
|
-
items.push({
|
|
509
|
-
header: "Free"
|
|
510
|
-
});
|
|
511
|
-
for (const row of freeRows) items.push({
|
|
512
|
-
row
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
418
|
}
|
|
516
419
|
return items;
|
|
517
420
|
});
|
|
@@ -663,10 +566,10 @@ export default Plugin.define({
|
|
|
663
566
|
trace("variant set: " + chosen + " -> " + applied);
|
|
664
567
|
}
|
|
665
568
|
function cycleSort() {
|
|
666
|
-
setSort((sort() + 1) %
|
|
569
|
+
setSort(ORDER[(ORDER.indexOf(sort()) + 1) % ORDER.length]);
|
|
667
570
|
}
|
|
668
571
|
function recentSort() {
|
|
669
|
-
setSort(
|
|
572
|
+
setSort("recent");
|
|
670
573
|
}
|
|
671
574
|
function toggleGroups() {
|
|
672
575
|
setGrouped(!grouped());
|
|
@@ -823,7 +726,7 @@ export default Plugin.define({
|
|
|
823
726
|
_$insertNode(_el$6, _el$8);
|
|
824
727
|
_$setProp(_el$6, "fg", DIM);
|
|
825
728
|
_$setProp(_el$6, "wrapMode", "none");
|
|
826
|
-
_$insert(_el$6, () => "sort: " +
|
|
729
|
+
_$insert(_el$6, () => "sort: " + ORDERS[sort()].short, _el$8);
|
|
827
730
|
_$insertNode(_el$9, _el$0);
|
|
828
731
|
_$setProp(_el$9, "fg", BASE);
|
|
829
732
|
_$setProp(_el$9, "wrapMode", "none");
|
|
@@ -899,29 +802,43 @@ export default Plugin.define({
|
|
|
899
802
|
return scope() !== "favorites" && prefs.favorite.has(key);
|
|
900
803
|
};
|
|
901
804
|
return (() => {
|
|
902
|
-
var _el$19 = _$createElement("box")
|
|
903
|
-
_el$20 = _$createElement("text");
|
|
904
|
-
_$insertNode(_el$19, _el$20);
|
|
805
|
+
var _el$19 = _$createElement("box");
|
|
905
806
|
_$setProp(_el$19, "id", "mp-" + key);
|
|
906
807
|
_$setProp(_el$19, "height", 1);
|
|
808
|
+
_$setProp(_el$19, "flexDirection", "row");
|
|
907
809
|
_$setProp(_el$19, "onMouseOver", () => hover(key));
|
|
908
810
|
_$setProp(_el$19, "onMouseMove", () => hover(key));
|
|
909
811
|
_$setProp(_el$19, "onMouseDown", () => {
|
|
910
812
|
setSelected(key);
|
|
911
813
|
choose(row);
|
|
912
814
|
});
|
|
913
|
-
_$
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
815
|
+
_$insert(_el$19, () => {
|
|
816
|
+
const parts = rowParts(row, mine(), favourite(), fitted());
|
|
817
|
+
const fg = active() ? ON_SELECTED : mine() ? ACCENT : BASE;
|
|
818
|
+
// Dim marks the borrowed figure; the tilde says it too, for a reader who
|
|
819
|
+
// cannot see the colour. Measured values keep the row's own colour.
|
|
820
|
+
const speedFg = active() ? ON_SELECTED : row.tps !== undefined ? DIM : fg;
|
|
821
|
+
return [(() => {
|
|
822
|
+
var _el$20 = _$createElement("text");
|
|
823
|
+
_$setProp(_el$20, "fg", fg);
|
|
824
|
+
_$setProp(_el$20, "wrapMode", "none");
|
|
825
|
+
_$insert(_el$20, () => parts.before);
|
|
826
|
+
return _el$20;
|
|
827
|
+
})(), _$memo(() => _$memo(() => !!parts.speed)() ? (() => {
|
|
828
|
+
var _el$21 = _$createElement("text");
|
|
829
|
+
_$setProp(_el$21, "fg", speedFg);
|
|
830
|
+
_$setProp(_el$21, "wrapMode", "none");
|
|
831
|
+
_$insert(_el$21, () => parts.speed);
|
|
832
|
+
return _el$21;
|
|
833
|
+
})() : null), _$memo(() => _$memo(() => !!parts.after)() ? (() => {
|
|
834
|
+
var _el$22 = _$createElement("text");
|
|
835
|
+
_$setProp(_el$22, "fg", fg);
|
|
836
|
+
_$setProp(_el$22, "wrapMode", "none");
|
|
837
|
+
_$insert(_el$22, () => parts.after);
|
|
838
|
+
return _el$22;
|
|
839
|
+
})() : null)];
|
|
924
840
|
});
|
|
841
|
+
_$effect(_$p => _$setProp(_el$19, "backgroundColor", active() ? SELECTED : undefined, _$p));
|
|
925
842
|
return _el$19;
|
|
926
843
|
})();
|
|
927
844
|
}
|
|
@@ -934,14 +851,14 @@ export default Plugin.define({
|
|
|
934
851
|
return hints();
|
|
935
852
|
},
|
|
936
853
|
children: line => (() => {
|
|
937
|
-
var _el$
|
|
938
|
-
_el$
|
|
939
|
-
_$insertNode(_el$
|
|
940
|
-
_$setProp(_el$
|
|
941
|
-
_$setProp(_el$
|
|
942
|
-
_$setProp(_el$
|
|
943
|
-
_$insert(_el$
|
|
944
|
-
return _el$
|
|
854
|
+
var _el$23 = _$createElement("box"),
|
|
855
|
+
_el$24 = _$createElement("text");
|
|
856
|
+
_$insertNode(_el$23, _el$24);
|
|
857
|
+
_$setProp(_el$23, "height", 1);
|
|
858
|
+
_$setProp(_el$24, "fg", DIM);
|
|
859
|
+
_$setProp(_el$24, "wrapMode", "none");
|
|
860
|
+
_$insert(_el$24, line);
|
|
861
|
+
return _el$23;
|
|
945
862
|
})()
|
|
946
863
|
}));
|
|
947
864
|
_$effect(_p$ => {
|