flexdesk 0.12.1 → 0.14.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 +86 -1
- package/css/flexdesk.css +176 -0
- package/dist/charts.js +4 -73
- package/dist/charts.js.map +3 -3
- package/dist/{chunk-2LEJQD4H.js → chunk-IYMNDPWH.js} +109 -2
- package/dist/chunk-IYMNDPWH.js.map +7 -0
- package/dist/flexdesk.css +176 -0
- package/dist/flow.js +3 -0
- package/dist/flow.js.map +2 -2
- package/dist/host.js +138 -0
- package/dist/host.js.map +3 -3
- package/dist/tiles.js +1752 -1031
- package/dist/tiles.js.map +4 -4
- package/dist/widgets.js +563 -5
- package/dist/widgets.js.map +3 -3
- package/dist/wm.js +146 -0
- package/dist/wm.js.map +3 -3
- package/package.json +1 -1
- package/src/charts/chart_types.js +40 -1
- package/src/flow/kit/dom.js +6 -0
- package/src/host/photino_host.js +176 -0
- package/src/host/photino_host.md +98 -0
- package/src/tiles/chart_tiles.js +827 -0
- package/src/tiles/tile_grid.js +4 -3
- package/src/tiling/bottom_panel_tabs.js +201 -0
- package/src/ui/components/alert_log.js +274 -0
- package/src/ui/components/alerts_pane.js +294 -0
- package/src/ui/components/notification_history.js +54 -4
- package/src/ui/utils/auto_scrollbars.js +30 -0
- package/dist/chunk-2LEJQD4H.js.map +0 -7
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ manager, and neither of them should drag in a plotting engine or a code editor.
|
|
|
45
45
|
| `@flexdesk/wm` | 166 kB | Tiling window manager: tabs, splits, desktops, command palette, taxonomy, `createShell()` |
|
|
46
46
|
| `@flexdesk/widgets` | 192 kB | DataTable, modals, managed windows, toasts, trees, panels, context menus |
|
|
47
47
|
| `@flexdesk/charts` | 26 kB | Plotly wrapper, chart types, subplot layouts, downsampling, plot windows |
|
|
48
|
-
| `@flexdesk/tiles` | 67 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence |
|
|
48
|
+
| `@flexdesk/tiles` | 67 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence, chart and KPI tiles |
|
|
49
49
|
| `@flexdesk/editor` | 62 kB | Monaco loader + factory, multi-file tab bar, search, split panes, undo manager |
|
|
50
50
|
| `@flexdesk/canvas` | 14 kB | The canvas kernel: a pannable, zoomable surface, the edge router, a generic graph layer ([Flow editors](#flow-editors)) |
|
|
51
51
|
| `@flexdesk/flow` | 381 kB | The flow-editor kit and the canvas, outline and lane editors ([Flow editors](#flow-editors)) |
|
|
@@ -1671,6 +1671,43 @@ const nc = new NotificationCenter({ eventBus, maxVisible: 3, overflow: 'collapse
|
|
|
1671
1671
|
nc.mount(document.body);
|
|
1672
1672
|
```
|
|
1673
1673
|
|
|
1674
|
+
### Alerts with history: a log, a pane, a tab host and a bell (0.14.0)
|
|
1675
|
+
|
|
1676
|
+
A toast is the moment. The **alert log** keeps what came in until it is
|
|
1677
|
+
dismissed, and four pieces draw it. Each works without the others:
|
|
1678
|
+
|
|
1679
|
+
| Piece | Entry point | Does |
|
|
1680
|
+
|---|---|---|
|
|
1681
|
+
| `createAlertLog({ source, limit })` | `widgets` | Entries `{id, at, level, title, body?, ref?, read}`, newest first; `add`, `receive`, `markRead`, `dismiss`, `unreadCount`, `worstUnread`, `subscribe`, `loadMore` |
|
|
1682
|
+
| `localSource({ read, write })` | `widgets` | Keeps the whole log in one stored value (localStorage, a workspace file) |
|
|
1683
|
+
| `mountAlertsPane(el, { log, onOpen, onBadge, rules?, stillMatches? })` | `widgets` | Level chips with counts, a filter, day groups, relative times, Open / Dismiss / Mark all read / Clear, *Load older* |
|
|
1684
|
+
| `createBottomPanelTabs(el, { panes, remember, store, eventBus })` | `wm` | The `.twm-bp` tab strip: `select`, `setBadge(id, n, level)`, `bottompanel:show {tab}` |
|
|
1685
|
+
| `new NotificationHistory({ alertLog, mountPoint, alertsTab, onOpen })` | `widgets` | The bell shows the log's unread count in the worst level's colour; a click (and "+N more") opens the Alerts tab |
|
|
1686
|
+
|
|
1687
|
+
A **source** is where entries live: `load({offset, limit})` answers
|
|
1688
|
+
`{entries, more?, unread?}`, plus `markRead(ids | null)` and, optionally,
|
|
1689
|
+
`dismiss(ids | null)`. Without `dismiss`, the pane offers no Dismiss or Clear.
|
|
1690
|
+
Levels are `info`, `warning` and `alert`; `warn`, `error` and `danger` are read as
|
|
1691
|
+
those.
|
|
1692
|
+
|
|
1693
|
+
```js
|
|
1694
|
+
const log = createAlertLog({ source: {
|
|
1695
|
+
load: ({ offset, limit }) => api.notifications({ offset, limit }),
|
|
1696
|
+
markRead: (ids) => (ids ? Promise.all(ids.map(api.readNotification)) : api.readAll()),
|
|
1697
|
+
} });
|
|
1698
|
+
const tabs = createBottomPanelTabs(panelEl, { eventBus, panes: [
|
|
1699
|
+
{ id: 'alerts', label: 'Alerts', eager: true,
|
|
1700
|
+
mount: (el, store, api) => mountAlertsPane(el, { log, onOpen: openRef,
|
|
1701
|
+
onBadge: ({ unread, level }) => api.setBadge('alerts', unread, level) }) },
|
|
1702
|
+
] });
|
|
1703
|
+
new NotificationHistory({ notificationCenter: nc, eventBus, alertLog: log, mountPoint: statusBarEl }).mount();
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
An eager pane, and the first one shown, mount inside `createBottomPanelTabs`
|
|
1707
|
+
before it returns. Reach the host through `mount`'s `api` argument, not the
|
|
1708
|
+
variable being assigned. Without `alertLog` and `mountPoint`, `NotificationHistory`
|
|
1709
|
+
is the bell it was.
|
|
1710
|
+
|
|
1674
1711
|
## The host port
|
|
1675
1712
|
|
|
1676
1713
|
FlexDesk never touches `pywebview`, `fetch`, `localStorage` or the filesystem. It
|
|
@@ -1688,6 +1725,22 @@ createHost({
|
|
|
1688
1725
|
|
|
1689
1726
|
`NULL_HOST` is a valid host with nothing behind it.
|
|
1690
1727
|
|
|
1728
|
+
Two adapters ship ready-made. `createPywebviewHost()` speaks to a pywebview
|
|
1729
|
+
window, and `createPhotinoHost()` (0.14.0) to a Photino.NET window over
|
|
1730
|
+
`window.external.sendMessage` / `receiveMessage`:
|
|
1731
|
+
|
|
1732
|
+
```js
|
|
1733
|
+
import { createPhotinoHost } from '@flexdesk/host';
|
|
1734
|
+
|
|
1735
|
+
// In a browser tab this is NULL_HOST. Keep a capability of your own by passing
|
|
1736
|
+
// it in, or leave one out with null.
|
|
1737
|
+
const host = createPhotinoHost({ state: myServerState, onEvent: (name, value) => {} });
|
|
1738
|
+
```
|
|
1739
|
+
|
|
1740
|
+
Its `window.chrome()` answers `'native'`, `dialogs.saveFile` falls back to a
|
|
1741
|
+
Blob download when the window refuses, and the messages the C# side has to
|
|
1742
|
+
answer are in [`src/host/photino_host.md`](src/host/photino_host.md).
|
|
1743
|
+
|
|
1691
1744
|
## Peer dependencies
|
|
1692
1745
|
|
|
1693
1746
|
`@flexdesk/charts` needs Plotly (4.4 MB). `@flexdesk/editor` needs Monaco (14 MB).
|
|
@@ -1731,6 +1784,38 @@ the configuration step is explicit rather than lazy.
|
|
|
1731
1784
|
A layout is keyed by *something* — a document, a user, a run — and the library has
|
|
1732
1785
|
no opinion about which. It calls that a `contextId`.
|
|
1733
1786
|
|
|
1787
|
+
### Chart and KPI tiles (0.13.0)
|
|
1788
|
+
|
|
1789
|
+
`@flexdesk/tiles` ships two widgets: `flexdesk.chart` (line, bar, area, scatter,
|
|
1790
|
+
pie, donut, funnel, histogram — Plotly, so `setPlotlySource` first) and
|
|
1791
|
+
`flexdesk.kpi` (one number, a label and an optional delta; plain DOM, no Plotly).
|
|
1792
|
+
Nothing registers at import — call `registerChartTiles()` (idempotent). You hand
|
|
1793
|
+
a tile **already-computed** series; turning records into series is yours:
|
|
1794
|
+
|
|
1795
|
+
```js
|
|
1796
|
+
import { registerChartTiles } from '@flexdesk/tiles';
|
|
1797
|
+
registerChartTiles();
|
|
1798
|
+
const chart = grid.addTile('flexdesk.chart', { x: 0, y: 0, w: 6, h: 4 },
|
|
1799
|
+
{ chart_type: 'line', title: 'Burndown', unit: 'pts' });
|
|
1800
|
+
chart.onPointClick = ({ customdata }) => openItem(customdata);
|
|
1801
|
+
chart.setData({
|
|
1802
|
+
series: [
|
|
1803
|
+
{ x: days, y: remaining, name: 'Remaining', interpolation: 'step', customdata: ids },
|
|
1804
|
+
{ x: days, y: ideal, name: 'Ideal', lineStyle: 'dashed' },
|
|
1805
|
+
],
|
|
1806
|
+
markers: [{ x: today, label: 'Today' }], // vertical lines
|
|
1807
|
+
note: 'From 41 stories', // a line under the figure
|
|
1808
|
+
});
|
|
1809
|
+
grid.addTile('flexdesk.kpi', { x: 6, y: 0, w: 3, h: 2 }, { title: 'Open', unit: 'bugs' })
|
|
1810
|
+
.setData({ value: 42, reference: 37 }); // ▲ +5 bugs
|
|
1811
|
+
```
|
|
1812
|
+
|
|
1813
|
+
`setData(null)` or an empty series is the empty state, and it releases any figure
|
|
1814
|
+
still drawing. A pie takes `{labels, values}`, a histogram `{values}`. The config
|
|
1815
|
+
(`chart_type`, `title`, `unit`, …) round-trips through `getLayout()`/`setLayout()`.
|
|
1816
|
+
`buildOverlayShapes` gained `verticalLines: [{x, label?, color?, lineStyle?}]` for
|
|
1817
|
+
the same marker outside a tile.
|
|
1818
|
+
|
|
1734
1819
|
## Styling
|
|
1735
1820
|
|
|
1736
1821
|
Three stylesheets, and that is the whole CSS contract:
|
package/css/flexdesk.css
CHANGED
|
@@ -394,6 +394,12 @@
|
|
|
394
394
|
padding: 0 3px;
|
|
395
395
|
line-height: 1;
|
|
396
396
|
}
|
|
397
|
+
/* On an alert log (STORY-0020): the worst unread level's colour, and the bell
|
|
398
|
+
* itself brighter while anything is unread. */
|
|
399
|
+
.twm-notification-history__badge--info { background: var(--color-info); }
|
|
400
|
+
.twm-notification-history__badge--warning { background: var(--color-warning); color: #1e1e1e; }
|
|
401
|
+
.twm-notification-history__badge--alert { background: var(--color-danger); }
|
|
402
|
+
.twm-notification-history__trigger--unread { color: var(--text-bright); }
|
|
397
403
|
.twm-notification-history__panel {
|
|
398
404
|
position: fixed;
|
|
399
405
|
z-index: 9999;
|
|
@@ -3448,6 +3454,83 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
3448
3454
|
.twm-tile-error .material-symbols-outlined {
|
|
3449
3455
|
font-size: 32px;
|
|
3450
3456
|
}
|
|
3457
|
+
|
|
3458
|
+
/* ── the chart and KPI tiles (0.13.0, src/tiles/chart_tiles.js) ──────── */
|
|
3459
|
+
/* The figure flexes into what `.tile-content`'s column leaves; the note takes
|
|
3460
|
+
its own line BELOW it, so the height chain above is untouched. */
|
|
3461
|
+
.fd-chart-tile {
|
|
3462
|
+
display: flex;
|
|
3463
|
+
flex-direction: column;
|
|
3464
|
+
flex: 1 1 auto;
|
|
3465
|
+
min-width: 0;
|
|
3466
|
+
min-height: 0;
|
|
3467
|
+
position: relative;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
/* Plotly puts `.js-plotly-plot` on the figure itself, and the rule above makes
|
|
3471
|
+
that `height: 100% !important` — which, beside a note, pushes the note out of
|
|
3472
|
+
the tile. Here the flex column sizes the figure instead. */
|
|
3473
|
+
.tile-content .fd-chart-tile.js-plotly-plot {
|
|
3474
|
+
height: auto !important;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
.fd-chart-tile__note {
|
|
3478
|
+
flex: 0 0 auto;
|
|
3479
|
+
padding: 2px 8px 0;
|
|
3480
|
+
font-size: var(--font-size-xs);
|
|
3481
|
+
line-height: 1.35;
|
|
3482
|
+
color: var(--text-muted);
|
|
3483
|
+
white-space: nowrap;
|
|
3484
|
+
overflow: hidden;
|
|
3485
|
+
text-overflow: ellipsis;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
.fd-kpi {
|
|
3489
|
+
--fd-kpi-value: 30px;
|
|
3490
|
+
flex: 1 1 auto;
|
|
3491
|
+
display: flex;
|
|
3492
|
+
flex-direction: column;
|
|
3493
|
+
align-items: flex-start;
|
|
3494
|
+
justify-content: center;
|
|
3495
|
+
gap: 2px;
|
|
3496
|
+
min-height: 0;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
.fd-kpi__label {
|
|
3500
|
+
color: var(--text-secondary);
|
|
3501
|
+
font-size: var(--font-size-xs);
|
|
3502
|
+
text-transform: uppercase;
|
|
3503
|
+
letter-spacing: 0.06em;
|
|
3504
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
.fd-kpi__value {
|
|
3508
|
+
font-size: var(--fd-kpi-value);
|
|
3509
|
+
font-weight: 700;
|
|
3510
|
+
color: var(--text-bright);
|
|
3511
|
+
line-height: 1.1;
|
|
3512
|
+
font-variant-numeric: tabular-nums;
|
|
3513
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
/* AN ARROW AND A SIGN, not a colour alone: the modifiers change the colour,
|
|
3517
|
+
the character in front of the number survives a monochrome display. */
|
|
3518
|
+
.fd-kpi__delta {
|
|
3519
|
+
display: inline-flex;
|
|
3520
|
+
align-items: center;
|
|
3521
|
+
gap: 3px;
|
|
3522
|
+
font-size: var(--font-size-sm);
|
|
3523
|
+
font-variant-numeric: tabular-nums;
|
|
3524
|
+
color: var(--text-secondary);
|
|
3525
|
+
}
|
|
3526
|
+
.fd-kpi__delta--up { color: var(--color-success); }
|
|
3527
|
+
.fd-kpi__delta--down { color: var(--color-danger); }
|
|
3528
|
+
|
|
3529
|
+
.fd-kpi__foot {
|
|
3530
|
+
color: var(--text-muted);
|
|
3531
|
+
font-size: var(--font-size-xs);
|
|
3532
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
|
|
3533
|
+
}
|
|
3451
3534
|
/* ===================================================================
|
|
3452
3535
|
REUSABLE ACTION DROPDOWN COMPONENT
|
|
3453
3536
|
Used by Add Scenario, Add Widget, and other action dropdowns
|
|
@@ -7660,6 +7743,28 @@ tr:hover .twm-row-action { opacity: 1; }
|
|
|
7660
7743
|
color: var(--text-bright);
|
|
7661
7744
|
border-bottom-color: #fff;
|
|
7662
7745
|
}
|
|
7746
|
+
/* A tab's count (createBottomPanelTabs' setBadge): Alerts' unread in the
|
|
7747
|
+
* worst unread level's colour, a Changes count in the neutral one. */
|
|
7748
|
+
.twm-bp__badge {
|
|
7749
|
+
display: inline-flex;
|
|
7750
|
+
align-items: center;
|
|
7751
|
+
justify-content: center;
|
|
7752
|
+
min-width: 16px;
|
|
7753
|
+
height: 16px;
|
|
7754
|
+
margin-left: 6px;
|
|
7755
|
+
padding: 0 4px;
|
|
7756
|
+
box-sizing: border-box;
|
|
7757
|
+
border-radius: 8px;
|
|
7758
|
+
background: var(--accent);
|
|
7759
|
+
color: #fff;
|
|
7760
|
+
font-size: 10.5px;
|
|
7761
|
+
line-height: 16px;
|
|
7762
|
+
text-transform: none;
|
|
7763
|
+
}
|
|
7764
|
+
.twm-bp__badge[hidden] { display: none; }
|
|
7765
|
+
.twm-bp__badge--info { background: color-mix(in srgb, var(--color-info) 35%, var(--surface-0)); }
|
|
7766
|
+
.twm-bp__badge--warning { background: color-mix(in srgb, var(--color-warning) 35%, var(--surface-0)); }
|
|
7767
|
+
.twm-bp__badge--alert { background: color-mix(in srgb, var(--color-danger) 45%, var(--surface-0)); }
|
|
7663
7768
|
.twm-bp__body {
|
|
7664
7769
|
flex: 1 1 auto;
|
|
7665
7770
|
min-height: 0;
|
|
@@ -7705,6 +7810,77 @@ tr:hover .twm-row-action { opacity: 1; }
|
|
|
7705
7810
|
.twm-bp-log__row--error .twm-bp-log__level,
|
|
7706
7811
|
.twm-bp-log__row--error .twm-bp-log__msg { color: var(--color-danger); }
|
|
7707
7812
|
.twm-bp-log__msg { color: var(--text-body); white-space: pre-wrap; word-break: break-word; }
|
|
7813
|
+
|
|
7814
|
+
/* ── The Alerts pane (alerts_pane.js, STORY-0019) ─────────────────────────
|
|
7815
|
+
* bugdesk's Alerts tab, on tokens: rules on the left when the embedder has
|
|
7816
|
+
* them, the log on the right, newest first, grouped by day. */
|
|
7817
|
+
.twm-alerts { display: flex; height: 100%; min-height: 0; font-size: var(--font-size-xs); color: var(--text-body); }
|
|
7818
|
+
/* In the bottom panel the pane fills the body, so the log scrolls and the
|
|
7819
|
+
* status line sits at the foot. Only a pane holding the Alerts pane: other
|
|
7820
|
+
* panes keep their padding and their own height. */
|
|
7821
|
+
.twm-bp-pane:has(> .twm-alerts) { height: 100%; padding: 0; box-sizing: border-box; }
|
|
7822
|
+
.twm-alerts .material-symbols-outlined { font-size: 15px; }
|
|
7823
|
+
.twm-alerts__spacer { flex: 1 1 auto; }
|
|
7824
|
+
.twm-alerts__rules { flex: 0 0 280px; display: flex; flex-direction: column; min-width: 0; border-right: 1px solid var(--border-subtle); }
|
|
7825
|
+
.twm-alerts__head, .twm-alerts__bar { flex: none; display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 12px; border-bottom: 1px solid var(--border-subtle); }
|
|
7826
|
+
.twm-alerts__bar { flex-wrap: wrap; }
|
|
7827
|
+
.twm-alerts__caps { color: var(--text-muted); font-size: 10.5px; letter-spacing: .08em; }
|
|
7828
|
+
.twm-alerts__link { padding: 0; border: 0; background: none; color: var(--accent-bright); font: inherit; cursor: pointer; }
|
|
7829
|
+
.twm-alerts__link:hover { text-decoration: underline; }
|
|
7830
|
+
.twm-alerts__rulelist { flex: 1 1 auto; overflow-y: auto; padding: 4px 0; }
|
|
7831
|
+
.twm-alerts__rulebtn { width: 100%; display: flex; align-items: center; gap: 10px; height: 30px; padding: 0 12px; border: 0; background: none; color: var(--text-body); font: inherit; cursor: pointer; }
|
|
7832
|
+
.twm-alerts__rulebtn:hover { background: var(--state-hover); }
|
|
7833
|
+
.twm-alerts__rulebtn--on { background: var(--state-selected); box-shadow: inset 2px 0 0 var(--accent-bright); }
|
|
7834
|
+
.twm-alerts__rulebtn--off { color: var(--text-dim); }
|
|
7835
|
+
.twm-alerts__dot { flex: none; width: 8px; height: 8px; border-radius: 50%; }
|
|
7836
|
+
.twm-alerts__dot--alert { background: var(--color-danger); }
|
|
7837
|
+
.twm-alerts__dot--warning { background: var(--color-warning); }
|
|
7838
|
+
.twm-alerts__dot--info { background: var(--color-info); }
|
|
7839
|
+
.twm-alerts__rulename { flex: 1; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
7840
|
+
.twm-alerts__meta { flex: none; color: var(--text-muted); font-size: 11px; }
|
|
7841
|
+
.twm-alerts__foot { padding: 6px 12px; border-top: 1px solid var(--border-subtle); color: var(--text-muted); font-size: 11px; line-height: 1.4; }
|
|
7842
|
+
.twm-alerts__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
|
|
7843
|
+
.twm-alerts__chips { display: flex; gap: 4px; }
|
|
7844
|
+
.twm-alerts__chip { height: 24px; padding: 0 10px; border: 1px solid var(--surface-3); border-radius: 12px; background: none; color: var(--text-secondary); font: inherit; cursor: pointer; }
|
|
7845
|
+
.twm-alerts__chip:hover { color: var(--text-bright); }
|
|
7846
|
+
.twm-alerts__count { color: var(--text-muted); }
|
|
7847
|
+
.twm-alerts__chip--on { border-color: var(--accent-bright); background: var(--state-selected); color: var(--text-bright); }
|
|
7848
|
+
.twm-alerts__q { width: 220px; height: 24px; box-sizing: border-box; padding: 0 8px; border: 1px solid var(--surface-3); border-radius: 3px; background: var(--surface-1); color: var(--text-body); font: inherit; }
|
|
7849
|
+
.twm-alerts__q:focus { outline: none; border-color: var(--accent-bright); }
|
|
7850
|
+
.twm-alerts__btn { height: 24px; }
|
|
7851
|
+
.twm-alerts__btn[hidden] { display: none; }
|
|
7852
|
+
.twm-alerts__log { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
7853
|
+
.twm-alerts__day { padding: 8px 12px 4px 34px; color: var(--text-muted); font-size: 10.5px; letter-spacing: .08em; }
|
|
7854
|
+
.twm-alerts__row { display: flex; align-items: center; gap: 10px; height: 30px; padding: 0 12px; }
|
|
7855
|
+
.twm-alerts__row:hover { background: var(--state-hover); }
|
|
7856
|
+
.twm-alerts__unread { flex: 0 0 7px; height: 7px; border-radius: 50%; }
|
|
7857
|
+
.twm-alerts__row--unread .twm-alerts__unread { background: var(--accent-bright); }
|
|
7858
|
+
.twm-alerts__icon { flex: 0 0 18px; }
|
|
7859
|
+
.twm-alerts__row--info .twm-alerts__icon { color: var(--color-info); }
|
|
7860
|
+
.twm-alerts__row--warning .twm-alerts__icon { color: var(--color-warning); }
|
|
7861
|
+
.twm-alerts__row--alert .twm-alerts__icon { color: var(--color-danger); }
|
|
7862
|
+
.twm-alerts__time { flex: 0 0 78px; color: var(--text-muted); white-space: nowrap; }
|
|
7863
|
+
.twm-alerts__rule { flex: 0 0 170px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-primary); }
|
|
7864
|
+
.twm-alerts__tag { flex: none; display: inline-flex; align-items: center; height: 18px; padding: 0 6px; border-radius: 3px; background: var(--surface-2); color: var(--text-secondary); font-size: 10.5px; }
|
|
7865
|
+
.twm-alerts__tag--none { display: none; }
|
|
7866
|
+
.twm-alerts__ref { flex: 0 0 auto; max-width: 140px; padding: 0; border: 0; background: none; font-family: var(--font-family-mono); font-size: 11.5px; color: var(--accent-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
|
|
7867
|
+
button.twm-alerts__ref { cursor: pointer; }
|
|
7868
|
+
button.twm-alerts__ref:hover { text-decoration: underline; }
|
|
7869
|
+
.twm-alerts__ref:empty { display: none; }
|
|
7870
|
+
.twm-alerts__text { flex: 1 1 auto; min-width: 0; display: flex; gap: 8px; overflow: hidden; white-space: nowrap; }
|
|
7871
|
+
.twm-alerts__title { overflow: hidden; text-overflow: ellipsis; color: var(--text-secondary); }
|
|
7872
|
+
.twm-alerts__row--unread .twm-alerts__title { color: var(--text-primary); font-weight: 500; }
|
|
7873
|
+
.twm-alerts__body { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--text-muted); }
|
|
7874
|
+
.twm-alerts__state { flex: 0 0 128px; font-size: 11px; color: var(--text-muted); }
|
|
7875
|
+
.twm-alerts__state--gone { color: var(--text-dim); font-style: italic; }
|
|
7876
|
+
.twm-alerts__acts { flex: 0 0 auto; display: flex; justify-content: flex-end; gap: 2px; }
|
|
7877
|
+
.twm-alerts__icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 3px; background: none; color: var(--text-muted); cursor: pointer; }
|
|
7878
|
+
.twm-alerts__icon-btn:hover { background: var(--surface-2); color: var(--text-bright); }
|
|
7879
|
+
.twm-alerts__icon-btn .material-symbols-outlined { font-size: 14px; }
|
|
7880
|
+
.twm-alerts__none { padding: 28px 16px; text-align: center; color: var(--text-muted); }
|
|
7881
|
+
.twm-alerts__more { display: block; margin: 8px auto; height: 24px; }
|
|
7882
|
+
.twm-alerts__status { flex: none; display: flex; align-items: center; height: 26px; padding: 0 12px; border-top: 1px solid var(--border-subtle); color: var(--text-muted); font-size: 11px; }
|
|
7883
|
+
.twm-alerts__status[hidden] { display: none; }
|
|
7708
7884
|
/* Context menu — override main_new.css's .twm-context-menu to the tighter,
|
|
7709
7885
|
* grid-style look (icon | label) with a flat surface-1 background and
|
|
7710
7886
|
* square corners. All callers (sidebars, tile chrome, desktop bar, …)
|
package/dist/charts.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
hasPlot,
|
|
28
28
|
hexToRgba,
|
|
29
29
|
is3DChart,
|
|
30
|
+
lttb,
|
|
30
31
|
makeDefaultSubplot,
|
|
31
32
|
migrateData,
|
|
32
33
|
migrateSingleChart,
|
|
@@ -37,9 +38,11 @@ import {
|
|
|
37
38
|
resize,
|
|
38
39
|
restyle,
|
|
39
40
|
setPlotlySource,
|
|
41
|
+
strideDownsampleShared,
|
|
42
|
+
strideIndices,
|
|
40
43
|
uid,
|
|
41
44
|
updateChart
|
|
42
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-IYMNDPWH.js";
|
|
43
46
|
import {
|
|
44
47
|
createRafResizeObserver
|
|
45
48
|
} from "./chunk-M2L4XW2U.js";
|
|
@@ -660,78 +663,6 @@ function closeDataSeriesPlotWindow(seriesId) {
|
|
|
660
663
|
_openPlotWindows.delete(seriesId);
|
|
661
664
|
}
|
|
662
665
|
}
|
|
663
|
-
|
|
664
|
-
// src/charts/downsample.js
|
|
665
|
-
function strideIndices(n, targetPoints) {
|
|
666
|
-
if (!Number.isFinite(n) || n <= 0) return null;
|
|
667
|
-
const target = Math.max(2, Math.floor(targetPoints) || 0);
|
|
668
|
-
if (n <= target) return null;
|
|
669
|
-
const step = (n - 1) / (target - 1);
|
|
670
|
-
const idx = new Array(target);
|
|
671
|
-
for (let i = 0; i < target; i++) idx[i] = Math.round(i * step);
|
|
672
|
-
idx[target - 1] = n - 1;
|
|
673
|
-
let w = 0;
|
|
674
|
-
for (let r = 0; r < idx.length; r++) {
|
|
675
|
-
if (w === 0 || idx[r] > idx[w - 1]) idx[w++] = idx[r];
|
|
676
|
-
}
|
|
677
|
-
idx.length = w;
|
|
678
|
-
return idx;
|
|
679
|
-
}
|
|
680
|
-
function strideDownsampleShared(time, series, targetPoints) {
|
|
681
|
-
const idx = strideIndices(time?.length ?? 0, targetPoints);
|
|
682
|
-
if (!idx) return { time, series };
|
|
683
|
-
const dsTime = idx.map((i) => time[i]);
|
|
684
|
-
const dsSeries = {};
|
|
685
|
-
for (const [name, arr] of Object.entries(series || {})) {
|
|
686
|
-
if (!Array.isArray(arr)) {
|
|
687
|
-
dsSeries[name] = arr;
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
dsSeries[name] = idx.map((i) => i < arr.length ? arr[i] : null);
|
|
691
|
-
}
|
|
692
|
-
return { time: dsTime, series: dsSeries };
|
|
693
|
-
}
|
|
694
|
-
function lttb(x, y, threshold) {
|
|
695
|
-
const n = Math.min(x?.length ?? 0, y?.length ?? 0);
|
|
696
|
-
const target = Math.floor(threshold) || 0;
|
|
697
|
-
if (target <= 0 || target >= n) return { x: x.slice(0, n), y: y.slice(0, n) };
|
|
698
|
-
if (target < 3) return { x: [x[0], x[n - 1]], y: [y[0], y[n - 1]] };
|
|
699
|
-
const num = (v) => Number.isFinite(v) ? v : 0;
|
|
700
|
-
const outX = [x[0]];
|
|
701
|
-
const outY = [y[0]];
|
|
702
|
-
const bucketSize = (n - 2) / (target - 2);
|
|
703
|
-
let a = 0;
|
|
704
|
-
for (let i = 0; i < target - 2; i++) {
|
|
705
|
-
let nextStart = Math.floor((i + 1) * bucketSize) + 1;
|
|
706
|
-
let nextEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, n);
|
|
707
|
-
if (nextEnd <= nextStart) nextEnd = Math.min(nextStart + 1, n);
|
|
708
|
-
let avgX = 0, avgY = 0;
|
|
709
|
-
const count = nextEnd - nextStart;
|
|
710
|
-
for (let j = nextStart; j < nextEnd; j++) {
|
|
711
|
-
avgX += x[j];
|
|
712
|
-
avgY += num(y[j]);
|
|
713
|
-
}
|
|
714
|
-
avgX /= count;
|
|
715
|
-
avgY /= count;
|
|
716
|
-
const curStart = Math.floor(i * bucketSize) + 1;
|
|
717
|
-
const curEnd = Math.min(Math.floor((i + 1) * bucketSize) + 1, n);
|
|
718
|
-
const ax = x[a], ay = num(y[a]);
|
|
719
|
-
let maxArea = -1, chosen = curStart;
|
|
720
|
-
for (let j = curStart; j < curEnd; j++) {
|
|
721
|
-
const area = Math.abs((ax - avgX) * (num(y[j]) - ay) - (ax - x[j]) * (avgY - ay));
|
|
722
|
-
if (area > maxArea) {
|
|
723
|
-
maxArea = area;
|
|
724
|
-
chosen = j;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
outX.push(x[chosen]);
|
|
728
|
-
outY.push(y[chosen]);
|
|
729
|
-
a = chosen;
|
|
730
|
-
}
|
|
731
|
-
outX.push(x[n - 1]);
|
|
732
|
-
outY.push(y[n - 1]);
|
|
733
|
-
return { x: outX, y: outY };
|
|
734
|
-
}
|
|
735
666
|
export {
|
|
736
667
|
ALL_CHART_TYPES,
|
|
737
668
|
CHART_TYPES_2D,
|