busybar-kit 0.1.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 +124 -0
- package/configs/eslint.config.js +58 -0
- package/configs/prettier.json +5 -0
- package/configs/tsconfig.base.json +20 -0
- package/dist/colors.d.ts +18 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +17 -0
- package/dist/colors.js.map +1 -0
- package/dist/config/bar.d.ts +51 -0
- package/dist/config/bar.d.ts.map +1 -0
- package/dist/config/bar.js +65 -0
- package/dist/config/bar.js.map +1 -0
- package/dist/config/env.d.ts +15 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +36 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/device.d.ts +48 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +67 -0
- package/dist/device.js.map +1 -0
- package/dist/elements.d.ts +14 -0
- package/dist/elements.d.ts.map +1 -0
- package/dist/elements.js +33 -0
- package/dist/elements.js.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -0
- package/dist/format.d.ts +11 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +66 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/preview/font.d.ts +7 -0
- package/dist/preview/font.d.ts.map +1 -0
- package/dist/preview/font.js +75 -0
- package/dist/preview/font.js.map +1 -0
- package/dist/preview/index.d.ts +4 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +4 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/preview/png.d.ts +18 -0
- package/dist/preview/png.d.ts.map +1 -0
- package/dist/preview/png.js +122 -0
- package/dist/preview/png.js.map +1 -0
- package/dist/preview/raster.d.ts +6 -0
- package/dist/preview/raster.d.ts.map +1 -0
- package/dist/preview/raster.js +97 -0
- package/dist/preview/raster.js.map +1 -0
- package/dist/ticker/index.d.ts +3 -0
- package/dist/ticker/index.d.ts.map +1 -0
- package/dist/ticker/index.js +3 -0
- package/dist/ticker/index.js.map +1 -0
- package/dist/ticker/text.d.ts +15 -0
- package/dist/ticker/text.d.ts.map +1 -0
- package/dist/ticker/text.js +97 -0
- package/dist/ticker/text.js.map +1 -0
- package/dist/ticker/ticker.d.ts +22 -0
- package/dist/ticker/ticker.d.ts.map +1 -0
- package/dist/ticker/ticker.js +33 -0
- package/dist/ticker/ticker.js.map +1 -0
- package/package.json +75 -0
- package/scripts/fix-esm-extensions.mjs +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# busybar-kit
|
|
2
|
+
|
|
3
|
+
Shared building blocks for [BUSY Bar](https://busy.bar) apps. Everything here is
|
|
4
|
+
app-agnostic: screen geometry, the palette neutrals, formatters, ticker text,
|
|
5
|
+
the Bar's error taxonomy, the connection half of a config, and an offline PNG
|
|
6
|
+
renderer that draws the same elements the device does.
|
|
7
|
+
|
|
8
|
+
It is the common half of [busybar-dota](https://github.com/viirtualp1/busybar-dota),
|
|
9
|
+
[busybar-mydota](https://github.com/viirtualp1/busybar-mydota) and
|
|
10
|
+
[busybar-livesplit](https://github.com/viirtualp1/busybar-livesplit).
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install busybar-kit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`@busy-app/busy-lib` is a peer dependency — the app owns that version.
|
|
17
|
+
|
|
18
|
+
## What is in it
|
|
19
|
+
|
|
20
|
+
| Import | What it gives you |
|
|
21
|
+
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `busybar-kit/device` | `FRONT` / `BACK` screen sizes, `FONT_WIDTH`, `textWidth`, `clipToWidth`, `fittingChars`, `rowY`, `backRowGrid` |
|
|
23
|
+
| `busybar-kit/colors` | `BASE_COLORS` — neutrals, chrome, and the transparent value that hides an element |
|
|
24
|
+
| `busybar-kit/format` | `formatClock`, `formatGold`, `formatCountdown`, `formatStartTime`, `formatKda`, `formatPercent`, … |
|
|
25
|
+
| `busybar-kit/elements` | `AnyElement`, `band()`, `fillWidth()` |
|
|
26
|
+
| `busybar-kit/ticker` | `paginate` / `pageAt` / `scrollAt` / `tickerLine`, and `EventTicker` |
|
|
27
|
+
| `busybar-kit/errors` | `BarApiError`, `toBarError`, `isLowPriority`, `isClientError`, `isForbidden` |
|
|
28
|
+
| `busybar-kit/config` | `loadEnvFile`, `envReader`, `loadBarConfig` |
|
|
29
|
+
| `busybar-kit/preview` | `renderFront` / `renderBack` / `Bitmap` — device-accurate PNGs with no hardware |
|
|
30
|
+
| `busybar-kit/tsconfig.json`, `busybar-kit/eslint`, `busybar-kit/prettier` | the shared toolchain |
|
|
31
|
+
| `busybar-fix-esm` (bin) | rewrites relative imports in `dist/` to carry `.js`, for Node ESM |
|
|
32
|
+
|
|
33
|
+
## Geometry is hardware, not layout
|
|
34
|
+
|
|
35
|
+
`FRONT` (72×16) and `BACK` (160×80, with its header rows and 12px grid) are
|
|
36
|
+
facts about the device. An app spreads its own named slots on top:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { FRONT as DEVICE, BACK as DEVICE_BACK } from 'busybar-kit/device';
|
|
40
|
+
|
|
41
|
+
export const FRONT = {
|
|
42
|
+
...DEVICE,
|
|
43
|
+
topY: 0,
|
|
44
|
+
bottomY: 11,
|
|
45
|
+
clockWidth: 24,
|
|
46
|
+
} as const;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
An app whose header is a different height lays out its own row grid, and reads
|
|
50
|
+
rows off that instead of the default:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { backRowGrid, rowY as gridRowY } from 'busybar-kit/device';
|
|
54
|
+
|
|
55
|
+
export const BACK = { ...DEVICE_BACK, ...backRowGrid(16), nameX: 8 } as const;
|
|
56
|
+
|
|
57
|
+
export const rowY = (index: number) => gridRowY(index, BACK);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`clipToWidth` takes the truncation mark as a fourth argument (`'.'` by
|
|
61
|
+
default, `'..'` where names are cut often enough to want the emphasis).
|
|
62
|
+
|
|
63
|
+
Colours work the same way — `BASE_COLORS` holds what every app needs, and the
|
|
64
|
+
app adds its own semantics:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { BASE_COLORS } from 'busybar-kit/colors';
|
|
68
|
+
|
|
69
|
+
export const COLORS = {
|
|
70
|
+
...BASE_COLORS,
|
|
71
|
+
radiant: '#3FBF5FFF',
|
|
72
|
+
dire: '#E14B3AFF',
|
|
73
|
+
} as const;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Config
|
|
77
|
+
|
|
78
|
+
`loadBarConfig` resolves which Bar to talk to and over which transport, and
|
|
79
|
+
warns about credentials that transport will ignore. That is all it covers —
|
|
80
|
+
render cadence and request timeouts are the app's own tempo (a speedrun timer
|
|
81
|
+
wants 60ms frames where a match ticker is happy at 200), so they stay in the
|
|
82
|
+
app's config with `DEFAULTS` / `LIMITS` as the values worth starting from.
|
|
83
|
+
|
|
84
|
+
It hands back the same env reader, so an app's own settings collect their
|
|
85
|
+
warnings into one list:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { loadBarConfig, loadEnvFile } from 'busybar-kit/config';
|
|
89
|
+
|
|
90
|
+
loadEnvFile();
|
|
91
|
+
|
|
92
|
+
export function loadConfig(env = process.env) {
|
|
93
|
+
const warnings: string[] = [];
|
|
94
|
+
const { bar, env: read } = loadBarConfig(env, warnings);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
warnings,
|
|
98
|
+
config: {
|
|
99
|
+
...bar,
|
|
100
|
+
gsiPort: read.number('GSI_PORT', 3080, { min: 1024, max: 65_535 }),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Preview
|
|
107
|
+
|
|
108
|
+
`renderFront` / `renderBack` rasterise the very element list you send to the
|
|
109
|
+
device — a 3×5 pixel font, the same anchoring rules, and the back panel
|
|
110
|
+
flattened to the 16 greys the real OLED shows. Useful for screenshots in a
|
|
111
|
+
README and for asserting a layout in tests without a Bar on the desk.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { renderFront } from 'busybar-kit/preview';
|
|
115
|
+
|
|
116
|
+
writeFileSync('front.png', renderFront(frontElements(frame)).scale(8).toPng());
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Scripts
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run check # lint + typecheck + test
|
|
123
|
+
npm run build # tsc + .js extension fixup
|
|
124
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import prettier from 'eslint-config-prettier';
|
|
3
|
+
import tseslint from 'typescript-eslint';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The lint rules every BUSY Bar app shares. `rootDir` is the app's own
|
|
7
|
+
* directory, so type-aware rules read that app's tsconfig:
|
|
8
|
+
*
|
|
9
|
+
* import { barEslintConfig } from 'busybar-kit/eslint';
|
|
10
|
+
* export default barEslintConfig(import.meta.dirname);
|
|
11
|
+
*/
|
|
12
|
+
export function barEslintConfig(rootDir, ...extra) {
|
|
13
|
+
return tseslint.config(
|
|
14
|
+
{ ignores: ['dist/**', 'node_modules/**', 'scripts/**'] },
|
|
15
|
+
js.configs.recommended,
|
|
16
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
17
|
+
{
|
|
18
|
+
languageOptions: {
|
|
19
|
+
parserOptions: {
|
|
20
|
+
project: ['./tsconfig.json'],
|
|
21
|
+
tsconfigRootDir: rootDir,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
rules: {
|
|
25
|
+
'@typescript-eslint/no-unused-vars': [
|
|
26
|
+
'error',
|
|
27
|
+
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
|
28
|
+
],
|
|
29
|
+
'@typescript-eslint/consistent-type-imports': [
|
|
30
|
+
'error',
|
|
31
|
+
{ fixStyle: 'inline-type-imports' },
|
|
32
|
+
],
|
|
33
|
+
'@typescript-eslint/no-floating-promises': 'error',
|
|
34
|
+
'@typescript-eslint/switch-exhaustiveness-check': 'error',
|
|
35
|
+
eqeqeq: ['error', 'always'],
|
|
36
|
+
'no-console': 'off',
|
|
37
|
+
'prefer-const': 'error',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
files: ['test/**/*.ts'],
|
|
42
|
+
rules: {
|
|
43
|
+
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
44
|
+
// node:test returns a promise that is meant to be left alone
|
|
45
|
+
'@typescript-eslint/no-floating-promises': 'off',
|
|
46
|
+
'@typescript-eslint/require-await': 'off',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
files: ['**/*.js'],
|
|
51
|
+
extends: [tseslint.configs.disableTypeChecked],
|
|
52
|
+
},
|
|
53
|
+
prettier,
|
|
54
|
+
...extra,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default barEslintConfig(import.meta.dirname);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "esnext",
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"target": "esnext",
|
|
6
|
+
"lib": ["esnext"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"noUncheckedIndexedAccess": true,
|
|
10
|
+
"exactOptionalPropertyTypes": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"noImplicitOverride": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noUncheckedSideEffectImports": true,
|
|
17
|
+
"moduleDetection": "force",
|
|
18
|
+
"skipLibCheck": true
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/colors.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The palette every app shares: neutrals, chrome and the transparent value the
|
|
3
|
+
* Bar uses to hide an element. App-specific semantics (team colours, ahead/
|
|
4
|
+
* behind, gold) are spread on top of this in the app's own colours module.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BASE_COLORS: {
|
|
7
|
+
readonly white: "#FFFFFFFF";
|
|
8
|
+
readonly transparent: "#00000000";
|
|
9
|
+
readonly muted: "#9AA0A6FF";
|
|
10
|
+
readonly dim: "#6B7075FF";
|
|
11
|
+
readonly backText: "#E6E6E6FF";
|
|
12
|
+
readonly backDivider: "#5A5A5AFF";
|
|
13
|
+
readonly clock: "#D8DCE0FF";
|
|
14
|
+
readonly ticker: "#FFFFFFFF";
|
|
15
|
+
readonly panelDark: "#000000FF";
|
|
16
|
+
};
|
|
17
|
+
export type BaseColor = keyof typeof BASE_COLORS;
|
|
18
|
+
//# sourceMappingURL=colors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC"}
|
package/dist/colors.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The palette every app shares: neutrals, chrome and the transparent value the
|
|
3
|
+
* Bar uses to hide an element. App-specific semantics (team colours, ahead/
|
|
4
|
+
* behind, gold) are spread on top of this in the app's own colours module.
|
|
5
|
+
*/
|
|
6
|
+
export const BASE_COLORS = {
|
|
7
|
+
white: '#FFFFFFFF',
|
|
8
|
+
transparent: '#00000000',
|
|
9
|
+
muted: '#9AA0A6FF',
|
|
10
|
+
dim: '#6B7075FF',
|
|
11
|
+
backText: '#E6E6E6FF',
|
|
12
|
+
backDivider: '#5A5A5AFF',
|
|
13
|
+
clock: '#D8DCE0FF',
|
|
14
|
+
ticker: '#FFFFFFFF',
|
|
15
|
+
panelDark: '#000000FF',
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=colors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.js","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,KAAK,EAAE,WAAW;IAClB,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE,WAAW;IAClB,GAAG,EAAE,WAAW;IAChB,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,WAAW;IACnB,SAAS,EAAE,WAAW;CACd,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type EnvReader } from './env.js';
|
|
2
|
+
export declare const DEFAULTS: {
|
|
3
|
+
readonly usbAddr: "10.0.4.20";
|
|
4
|
+
readonly cloudAddr: "https://api.busy.app";
|
|
5
|
+
readonly drawPriority: 40;
|
|
6
|
+
readonly frameMs: 200;
|
|
7
|
+
readonly requestTimeoutMs: 10000;
|
|
8
|
+
};
|
|
9
|
+
export declare const LIMITS: {
|
|
10
|
+
readonly drawPriority: {
|
|
11
|
+
readonly min: 0;
|
|
12
|
+
readonly max: 100;
|
|
13
|
+
};
|
|
14
|
+
readonly frameMs: {
|
|
15
|
+
readonly min: 50;
|
|
16
|
+
readonly max: 2000;
|
|
17
|
+
};
|
|
18
|
+
readonly requestTimeoutMs: {
|
|
19
|
+
readonly min: 1000;
|
|
20
|
+
readonly max: 30000;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* How an app reaches the Bar, and nothing else. Render cadence and request
|
|
25
|
+
* timeouts are the app's own tempo — a speedrun timer wants 60ms frames where
|
|
26
|
+
* a match ticker is happy at 200 — so they stay in the app's config, with
|
|
27
|
+
* `DEFAULTS` / `LIMITS` here as the values worth starting from.
|
|
28
|
+
*/
|
|
29
|
+
export type BarConfig = {
|
|
30
|
+
busyAddr: string;
|
|
31
|
+
isCloud: boolean;
|
|
32
|
+
isUsb: boolean;
|
|
33
|
+
busyToken: string;
|
|
34
|
+
busyHttpPassword: string;
|
|
35
|
+
drawPriority: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function isCloudAddr(addr: string): boolean;
|
|
38
|
+
export declare function isUsbAddr(addr: string): boolean;
|
|
39
|
+
export type LoadedBarConfig = {
|
|
40
|
+
bar: BarConfig;
|
|
41
|
+
warnings: string[];
|
|
42
|
+
env: EnvReader;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the connection half of an app's config: which Bar, over which
|
|
46
|
+
* transport, with which credential — and warns about credentials that will be
|
|
47
|
+
* ignored on the transport in use. Apps build their own config on top, reusing
|
|
48
|
+
* the returned reader so their warnings land in the same list.
|
|
49
|
+
*/
|
|
50
|
+
export declare function loadBarConfig(env?: NodeJS.ProcessEnv, warnings?: string[]): LoadedBarConfig;
|
|
51
|
+
//# sourceMappingURL=bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bar.d.ts","sourceRoot":"","sources":["../../src/config/bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElD,eAAO,MAAM,QAAQ;;;;;;CAMX,CAAC;AAEX,eAAO,MAAM,MAAM;;;;;;;;;;;;;CAIT,CAAC;AAEX;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,WAEvC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,WAOrC;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,SAAS,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,EAAE,SAAS,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,MAAM,EAAO,GACtB,eAAe,CA0CjB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { envReader } from './env.js';
|
|
2
|
+
export const DEFAULTS = {
|
|
3
|
+
usbAddr: '10.0.4.20',
|
|
4
|
+
cloudAddr: 'https://api.busy.app',
|
|
5
|
+
drawPriority: 40,
|
|
6
|
+
frameMs: 200,
|
|
7
|
+
requestTimeoutMs: 10_000,
|
|
8
|
+
};
|
|
9
|
+
export const LIMITS = {
|
|
10
|
+
drawPriority: { min: 0, max: 100 },
|
|
11
|
+
frameMs: { min: 50, max: 2000 },
|
|
12
|
+
requestTimeoutMs: { min: 1000, max: 30_000 },
|
|
13
|
+
};
|
|
14
|
+
export function isCloudAddr(addr) {
|
|
15
|
+
return /api(?:\.(?:dev|test|stage))?\.busy\.app/i.test(addr);
|
|
16
|
+
}
|
|
17
|
+
export function isUsbAddr(addr) {
|
|
18
|
+
try {
|
|
19
|
+
const url = /^https?:\/\//i.test(addr) ? new URL(addr) : new URL(`http://${addr}`);
|
|
20
|
+
return url.hostname === DEFAULTS.usbAddr;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return addr.includes(DEFAULTS.usbAddr);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the connection half of an app's config: which Bar, over which
|
|
28
|
+
* transport, with which credential — and warns about credentials that will be
|
|
29
|
+
* ignored on the transport in use. Apps build their own config on top, reusing
|
|
30
|
+
* the returned reader so their warnings land in the same list.
|
|
31
|
+
*/
|
|
32
|
+
export function loadBarConfig(env = process.env, warnings = []) {
|
|
33
|
+
const reader = envReader(env, warnings);
|
|
34
|
+
const { read, number } = reader;
|
|
35
|
+
const token = read('BUSY_TOKEN');
|
|
36
|
+
const httpPassword = read('BUSY_HTTP_PASSWORD');
|
|
37
|
+
const busyAddr = read('BUSY_ADDR') || (token ? DEFAULTS.cloudAddr : DEFAULTS.usbAddr);
|
|
38
|
+
const cloud = isCloudAddr(busyAddr);
|
|
39
|
+
const usb = isUsbAddr(busyAddr);
|
|
40
|
+
if (cloud && httpPassword) {
|
|
41
|
+
warnings.push('BUSY_HTTP_PASSWORD is ignored on cloud, only BUSY_TOKEN is used');
|
|
42
|
+
}
|
|
43
|
+
if (!cloud && token) {
|
|
44
|
+
warnings.push(`BUSY_TOKEN is ignored for ${busyAddr}, that token only works on cloud`);
|
|
45
|
+
}
|
|
46
|
+
if (usb && httpPassword) {
|
|
47
|
+
warnings.push('BUSY_HTTP_PASSWORD is ignored over USB, no auth is required there');
|
|
48
|
+
}
|
|
49
|
+
if (!cloud && !usb && !httpPassword) {
|
|
50
|
+
warnings.push('Wi-Fi needs BUSY_HTTP_PASSWORD (Bar web UI → Network → HTTP API access)');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
warnings,
|
|
54
|
+
env: reader,
|
|
55
|
+
bar: {
|
|
56
|
+
busyAddr,
|
|
57
|
+
isCloud: cloud,
|
|
58
|
+
isUsb: usb,
|
|
59
|
+
busyToken: cloud ? token : '',
|
|
60
|
+
busyHttpPassword: cloud || usb ? '' : httpPassword,
|
|
61
|
+
drawPriority: number('DRAW_PRIORITY', DEFAULTS.drawPriority, LIMITS.drawPriority),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bar.js","sourceRoot":"","sources":["../../src/config/bar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Limits = {
|
|
2
|
+
min: number;
|
|
3
|
+
max: number;
|
|
4
|
+
};
|
|
5
|
+
export declare function loadEnvFile(cwd?: string): void;
|
|
6
|
+
export type EnvReader = {
|
|
7
|
+
read: (name: string) => string;
|
|
8
|
+
number: (name: string, fallback: number, limits: Limits, round?: boolean) => number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Reads settings out of the environment, clamping numbers into range and
|
|
12
|
+
* collecting a human-readable warning for anything it had to correct.
|
|
13
|
+
*/
|
|
14
|
+
export declare function envReader(env: NodeJS.ProcessEnv, warnings: string[]): EnvReader;
|
|
15
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAElD,wBAAgB,WAAW,CAAC,GAAG,SAAgB,QAO9C;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC;CACrF,CAAC;AAEF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,CAwB/E"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export function loadEnvFile(cwd = process.cwd()) {
|
|
4
|
+
const path = resolve(cwd, '.env');
|
|
5
|
+
if (!existsSync(path)) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
// Real environment variables win over the file, same as `node --env-file`.
|
|
9
|
+
process.loadEnvFile(path);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reads settings out of the environment, clamping numbers into range and
|
|
13
|
+
* collecting a human-readable warning for anything it had to correct.
|
|
14
|
+
*/
|
|
15
|
+
export function envReader(env, warnings) {
|
|
16
|
+
const read = (name) => env[name]?.trim() ?? '';
|
|
17
|
+
const number = (name, fallback, limits, round = true) => {
|
|
18
|
+
const raw = read(name);
|
|
19
|
+
if (!raw) {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
const value = Number(raw);
|
|
23
|
+
if (!Number.isFinite(value)) {
|
|
24
|
+
warnings.push(`${name}=${raw} is not a number, using ${fallback}`);
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
const wanted = round ? Math.round(value) : value;
|
|
28
|
+
const clamped = Math.min(limits.max, Math.max(limits.min, wanted));
|
|
29
|
+
if (clamped !== value) {
|
|
30
|
+
warnings.push(`${name}=${raw} is out of range ${limits.min}..${limits.max}, using ${clamped}`);
|
|
31
|
+
}
|
|
32
|
+
return clamped;
|
|
33
|
+
};
|
|
34
|
+
return { read, number };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,UAAU,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IACD,2EAA2E;IAC3E,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAsB,EAAE,QAAkB;IAClE,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAEvD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAE,MAAc,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE;QAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,GAAG,2BAA2B,QAAQ,EAAE,CAAC,CAAC;YACnE,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QACnE,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CACX,GAAG,IAAI,IAAI,GAAG,oBAAoB,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,WAAW,OAAO,EAAE,CAChF,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC"}
|
package/dist/device.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physical facts about the BUSY Bar screens. These are hardware, not app
|
|
3
|
+
* choices: every app draws into the same 72x16 front strip and 160x80 back
|
|
4
|
+
* panel. Per-app named slots (where the clock sits, how wide a hero column is)
|
|
5
|
+
* belong in the app's own layout module, spread on top of these.
|
|
6
|
+
*/
|
|
7
|
+
export declare const FRONT: {
|
|
8
|
+
readonly width: 72;
|
|
9
|
+
readonly height: 16;
|
|
10
|
+
};
|
|
11
|
+
export declare const BACK: {
|
|
12
|
+
readonly width: 160;
|
|
13
|
+
readonly height: 80;
|
|
14
|
+
readonly headerY: 2;
|
|
15
|
+
readonly subHeaderY: 9;
|
|
16
|
+
readonly firstRowY: 18;
|
|
17
|
+
readonly rowHeight: 12;
|
|
18
|
+
readonly maxRows: number;
|
|
19
|
+
readonly leftX: 2;
|
|
20
|
+
readonly rightX: 82;
|
|
21
|
+
readonly columnWidth: 76;
|
|
22
|
+
};
|
|
23
|
+
export type BarFont = 'tiny' | 'small' | 'bold';
|
|
24
|
+
/**
|
|
25
|
+
* Upper bound per glyph. The device fonts may be narrower, so clipping is
|
|
26
|
+
* conservative: text can come out shorter than strictly necessary, never wider
|
|
27
|
+
* than its box.
|
|
28
|
+
*/
|
|
29
|
+
export declare const FONT_WIDTH: Record<BarFont, number>;
|
|
30
|
+
export declare function textWidth(text: string, font: BarFont): number;
|
|
31
|
+
export declare function clipToWidth(text: string, widthPx: number, font: BarFont, ellipsis?: string): string;
|
|
32
|
+
export declare function fittingChars(widthPx: number, glyphWidth: number): number;
|
|
33
|
+
export type RowGeometry = {
|
|
34
|
+
firstRowY: number;
|
|
35
|
+
rowHeight: number;
|
|
36
|
+
};
|
|
37
|
+
/** Top edge of back-panel row `index`. Apps with a different grid pass their own. */
|
|
38
|
+
export declare function rowY(index: number, geometry?: RowGeometry): number;
|
|
39
|
+
/**
|
|
40
|
+
* A back-panel row grid: how many rows of `rowHeight` fit below `firstRowY`.
|
|
41
|
+
* An app with a taller header (or none) starts its rows somewhere else.
|
|
42
|
+
*/
|
|
43
|
+
export declare function backRowGrid(firstRowY: number, rowHeight?: number, height?: number): {
|
|
44
|
+
readonly firstRowY: number;
|
|
45
|
+
readonly rowHeight: number;
|
|
46
|
+
readonly maxRows: number;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;CAGR,CAAC;AAMX,eAAO,MAAM,IAAI;;;;;;;;;;;CAWP,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAI9C,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,UAEpD;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,EACb,QAAQ,SAAM,UAYf;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAE/D;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,qFAAqF;AACrF,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,WAAkB,UAE/D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAuB,EAClC,MAAM,GAAE,MAAoB;;;;EAO7B"}
|
package/dist/device.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physical facts about the BUSY Bar screens. These are hardware, not app
|
|
3
|
+
* choices: every app draws into the same 72x16 front strip and 160x80 back
|
|
4
|
+
* panel. Per-app named slots (where the clock sits, how wide a hero column is)
|
|
5
|
+
* belong in the app's own layout module, spread on top of these.
|
|
6
|
+
*/
|
|
7
|
+
export const FRONT = {
|
|
8
|
+
width: 72,
|
|
9
|
+
height: 16,
|
|
10
|
+
};
|
|
11
|
+
const BACK_HEIGHT = 80;
|
|
12
|
+
const BACK_FIRST_ROW_Y = 18;
|
|
13
|
+
const BACK_ROW_HEIGHT = 12;
|
|
14
|
+
export const BACK = {
|
|
15
|
+
width: 160,
|
|
16
|
+
height: BACK_HEIGHT,
|
|
17
|
+
headerY: 2,
|
|
18
|
+
subHeaderY: 9,
|
|
19
|
+
firstRowY: BACK_FIRST_ROW_Y,
|
|
20
|
+
rowHeight: BACK_ROW_HEIGHT,
|
|
21
|
+
maxRows: Math.floor((BACK_HEIGHT - BACK_FIRST_ROW_Y) / BACK_ROW_HEIGHT),
|
|
22
|
+
leftX: 2,
|
|
23
|
+
rightX: 82,
|
|
24
|
+
columnWidth: 76,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Upper bound per glyph. The device fonts may be narrower, so clipping is
|
|
28
|
+
* conservative: text can come out shorter than strictly necessary, never wider
|
|
29
|
+
* than its box.
|
|
30
|
+
*/
|
|
31
|
+
export const FONT_WIDTH = {
|
|
32
|
+
tiny: 4,
|
|
33
|
+
small: 4,
|
|
34
|
+
bold: 8,
|
|
35
|
+
};
|
|
36
|
+
export function textWidth(text, font) {
|
|
37
|
+
return text.length * FONT_WIDTH[font];
|
|
38
|
+
}
|
|
39
|
+
export function clipToWidth(text, widthPx, font, ellipsis = '.') {
|
|
40
|
+
const maxChars = Math.max(1, Math.floor(widthPx / FONT_WIDTH[font]));
|
|
41
|
+
if (text.length <= maxChars) {
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
if (maxChars <= 2 || maxChars <= ellipsis.length) {
|
|
45
|
+
return text.slice(0, maxChars);
|
|
46
|
+
}
|
|
47
|
+
return `${text.slice(0, maxChars - ellipsis.length)}${ellipsis}`;
|
|
48
|
+
}
|
|
49
|
+
export function fittingChars(widthPx, glyphWidth) {
|
|
50
|
+
return Math.max(0, Math.floor(widthPx / glyphWidth));
|
|
51
|
+
}
|
|
52
|
+
/** Top edge of back-panel row `index`. Apps with a different grid pass their own. */
|
|
53
|
+
export function rowY(index, geometry = BACK) {
|
|
54
|
+
return geometry.firstRowY + index * geometry.rowHeight;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A back-panel row grid: how many rows of `rowHeight` fit below `firstRowY`.
|
|
58
|
+
* An app with a taller header (or none) starts its rows somewhere else.
|
|
59
|
+
*/
|
|
60
|
+
export function backRowGrid(firstRowY, rowHeight = BACK.rowHeight, height = BACK.height) {
|
|
61
|
+
return {
|
|
62
|
+
firstRowY,
|
|
63
|
+
rowHeight,
|
|
64
|
+
maxRows: Math.floor((height - firstRowY) / rowHeight),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;CACF,CAAC;AAEX,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,gBAAgB;IAC3B,SAAS,EAAE,eAAe;IAC1B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,gBAAgB,CAAC,GAAG,eAAe,CAAC;IACvE,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,EAAE;IACV,WAAW,EAAE,EAAE;CACP,CAAC;AAIX;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAA4B;IACjD,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;CACR,CAAC;AAEF,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAa;IACnD,OAAO,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,OAAe,EACf,IAAa,EACb,QAAQ,GAAG,GAAG;IAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ,IAAI,CAAC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,UAAkB;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC;AACvD,CAAC;AAID,qFAAqF;AACrF,MAAM,UAAU,IAAI,CAAC,KAAa,EAAE,WAAwB,IAAI;IAC9D,OAAO,QAAQ,CAAC,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,SAAiB,EACjB,YAAoB,IAAI,CAAC,SAAS,EAClC,SAAiB,IAAI,CAAC,MAAM;IAE5B,OAAO;QACL,SAAS;QACT,SAAS;QACT,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;KAC7C,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RectangleElement, TextElement } from '@busy-app/busy-lib';
|
|
2
|
+
export type AnyElement = TextElement | RectangleElement;
|
|
3
|
+
/**
|
|
4
|
+
* A full-height coloured band across part of the front strip. Both Dota apps
|
|
5
|
+
* use one pair of these to show who is ahead.
|
|
6
|
+
*/
|
|
7
|
+
export declare function band(id: string, x: number, width: number, color: string, height?: number): RectangleElement;
|
|
8
|
+
export declare const MIN_FILL = 6;
|
|
9
|
+
/**
|
|
10
|
+
* Split a strip between two sides by `share` (0..1), always leaving both
|
|
11
|
+
* colours visible so the bar never reads as a single solid block.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fillWidth(share: number, width?: number): number;
|
|
14
|
+
//# sourceMappingURL=elements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIxE,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAExD;;;GAGG;AACH,wBAAgB,IAAI,CAClB,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,MAAM,EACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAqB,GAC5B,gBAAgB,CAgBlB;AAED,eAAO,MAAM,QAAQ,IAAI,CAAC;AAE1B;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAoB,UAInE"}
|
package/dist/elements.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BASE_COLORS } from './colors.js';
|
|
2
|
+
import { FRONT } from './device.js';
|
|
3
|
+
/**
|
|
4
|
+
* A full-height coloured band across part of the front strip. Both Dota apps
|
|
5
|
+
* use one pair of these to show who is ahead.
|
|
6
|
+
*/
|
|
7
|
+
export function band(id, x, width, color, height = FRONT.height) {
|
|
8
|
+
return {
|
|
9
|
+
id,
|
|
10
|
+
type: 'rectangle',
|
|
11
|
+
display: 'front',
|
|
12
|
+
align: 'top_left',
|
|
13
|
+
x,
|
|
14
|
+
y: 0,
|
|
15
|
+
width: Math.max(1, width),
|
|
16
|
+
height,
|
|
17
|
+
fill: 'solid',
|
|
18
|
+
fill_colors: [color],
|
|
19
|
+
border_width: 0,
|
|
20
|
+
border_color: BASE_COLORS.transparent,
|
|
21
|
+
timeout: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export const MIN_FILL = 6;
|
|
25
|
+
/**
|
|
26
|
+
* Split a strip between two sides by `share` (0..1), always leaving both
|
|
27
|
+
* colours visible so the bar never reads as a single solid block.
|
|
28
|
+
*/
|
|
29
|
+
export function fillWidth(share, width = FRONT.width) {
|
|
30
|
+
const raw = Math.round(Math.max(0, Math.min(1, share)) * width);
|
|
31
|
+
return Math.max(MIN_FILL, Math.min(width - MIN_FILL, raw));
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=elements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elements.js","sourceRoot":"","sources":["../src/elements.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAIjC;;;GAGG;AACH,MAAM,UAAU,IAAI,CAClB,EAAU,EACV,CAAS,EACT,KAAa,EACb,KAAa,EACb,SAAiB,KAAK,CAAC,MAAM;IAE7B,OAAO;QACL,EAAE;QACF,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,UAAU;QACjB,CAAC;QACD,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC;QACzB,MAAM;QACN,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,CAAC,KAAK,CAAC;QACpB,YAAY,EAAE,CAAC;QACf,YAAY,EAAE,WAAW,CAAC,WAAW;QACrC,OAAO,EAAE,CAAC;KACX,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC;AAE1B;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,QAAgB,KAAK,CAAC,KAAK;IAClE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IAEhE,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;AAC7D,CAAC"}
|