ember-mug 0.1.3 → 0.1.4
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/dist/cli.js +37 -7
- package/dist/components/App.d.ts +1 -1
- package/dist/components/App.js +64 -53
- package/dist/components/BatteryDisplay.d.ts +8 -3
- package/dist/components/BatteryDisplay.js +7 -18
- package/dist/components/ConnectionStatus.d.ts +7 -2
- package/dist/components/ConnectionStatus.js +5 -4
- package/dist/components/Header.d.ts +7 -2
- package/dist/components/Header.js +11 -3
- package/dist/components/HelpDisplay.d.ts +5 -2
- package/dist/components/HelpDisplay.js +8 -3
- package/dist/components/HorizontalRule.d.ts +2 -0
- package/dist/components/HorizontalRule.js +7 -0
- package/dist/components/Panel.d.ts +18 -0
- package/dist/components/Panel.js +57 -0
- package/dist/components/Presets.d.ts +8 -3
- package/dist/components/Presets.js +13 -8
- package/dist/components/SettingsView.d.ts +9 -3
- package/dist/components/SettingsView.js +82 -16
- package/dist/components/TemperatureControl.d.ts +8 -3
- package/dist/components/TemperatureControl.js +13 -18
- package/dist/components/TemperatureDisplay.d.ts +8 -3
- package/dist/components/TemperatureDisplay.js +8 -35
- package/dist/hooks/useMug.d.ts +1 -1
- package/dist/hooks/useMug.js +22 -22
- package/dist/lib/bluetooth.d.ts +2 -0
- package/dist/lib/bluetooth.js +8 -0
- package/dist/lib/mock-bluetooth.d.ts +65 -0
- package/dist/lib/mock-bluetooth.js +214 -0
- package/dist/lib/settings.d.ts +1 -1
- package/dist/lib/settings.js +20 -20
- package/dist/lib/theme.d.ts +135 -0
- package/dist/lib/theme.js +112 -0
- package/dist/lib/types.d.ts +0 -1
- package/dist/lib/types.js +12 -12
- package/dist/lib/utils.d.ts +1 -2
- package/dist/lib/utils.js +19 -35
- package/package.json +2 -1
- package/dist/components/ColorControl.d.ts +0 -9
- package/dist/components/ColorControl.js +0 -71
package/dist/lib/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TemperatureUnit, LiquidState, MIN_TEMP_CELSIUS, MAX_TEMP_CELSIUS, BATTERY_DRAIN_RATE_HEATING, BATTERY_DRAIN_RATE_MAINTAINING, BATTERY_CHARGE_RATE, } from
|
|
1
|
+
import { TemperatureUnit, LiquidState, MIN_TEMP_CELSIUS, MAX_TEMP_CELSIUS, BATTERY_DRAIN_RATE_HEATING, BATTERY_DRAIN_RATE_MAINTAINING, BATTERY_CHARGE_RATE, } from "./types.js";
|
|
2
2
|
export function formatTemperature(temp, unit) {
|
|
3
3
|
if (unit === TemperatureUnit.Celsius) {
|
|
4
4
|
return `${temp.toFixed(1)}°C`;
|
|
@@ -13,52 +13,36 @@ export function fahrenheitToCelsius(fahrenheit) {
|
|
|
13
13
|
return ((fahrenheit - 32) * 5) / 9;
|
|
14
14
|
}
|
|
15
15
|
export function formatBatteryLevel(level) {
|
|
16
|
-
return `${level}%`;
|
|
16
|
+
return `${Math.round(level)}%`;
|
|
17
17
|
}
|
|
18
18
|
export function getBatteryIcon(level, isCharging) {
|
|
19
19
|
if (isCharging) {
|
|
20
|
-
return
|
|
20
|
+
return "~";
|
|
21
21
|
}
|
|
22
22
|
if (level >= 75)
|
|
23
|
-
return
|
|
23
|
+
return "||||";
|
|
24
24
|
if (level >= 50)
|
|
25
|
-
return
|
|
25
|
+
return "|||.";
|
|
26
26
|
if (level >= 25)
|
|
27
|
-
return
|
|
27
|
+
return "||..";
|
|
28
28
|
if (level >= 10)
|
|
29
|
-
return
|
|
30
|
-
return
|
|
29
|
+
return "|...";
|
|
30
|
+
return "....";
|
|
31
31
|
}
|
|
32
32
|
export function getLiquidStateText(state) {
|
|
33
33
|
switch (state) {
|
|
34
34
|
case LiquidState.Empty:
|
|
35
|
-
return
|
|
35
|
+
return "Empty";
|
|
36
36
|
case LiquidState.Filling:
|
|
37
|
-
return
|
|
37
|
+
return "Filling";
|
|
38
38
|
case LiquidState.Cooling:
|
|
39
|
-
return
|
|
39
|
+
return "Cooling";
|
|
40
40
|
case LiquidState.Heating:
|
|
41
|
-
return
|
|
41
|
+
return "Heating";
|
|
42
42
|
case LiquidState.StableTemperature:
|
|
43
|
-
return
|
|
43
|
+
return "Perfect Temperature";
|
|
44
44
|
default:
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
export function getLiquidStateIcon(state) {
|
|
49
|
-
switch (state) {
|
|
50
|
-
case LiquidState.Empty:
|
|
51
|
-
return '○';
|
|
52
|
-
case LiquidState.Filling:
|
|
53
|
-
return '◐';
|
|
54
|
-
case LiquidState.Cooling:
|
|
55
|
-
return '❄';
|
|
56
|
-
case LiquidState.Heating:
|
|
57
|
-
return '🔥';
|
|
58
|
-
case LiquidState.StableTemperature:
|
|
59
|
-
return '✓';
|
|
60
|
-
default:
|
|
61
|
-
return '?';
|
|
45
|
+
return "Unknown";
|
|
62
46
|
}
|
|
63
47
|
}
|
|
64
48
|
export function clampTemperature(temp) {
|
|
@@ -66,7 +50,7 @@ export function clampTemperature(temp) {
|
|
|
66
50
|
}
|
|
67
51
|
export function formatDuration(minutes) {
|
|
68
52
|
if (minutes < 1) {
|
|
69
|
-
return
|
|
53
|
+
return "< 1 min";
|
|
70
54
|
}
|
|
71
55
|
if (minutes < 60) {
|
|
72
56
|
return `${Math.round(minutes)} min`;
|
|
@@ -122,12 +106,12 @@ export function estimateBatteryLife(batteryLevel, isCharging, liquidState) {
|
|
|
122
106
|
export function getTemperatureColor(currentTemp, targetTemp) {
|
|
123
107
|
const diff = currentTemp - targetTemp;
|
|
124
108
|
if (Math.abs(diff) < 1) {
|
|
125
|
-
return
|
|
109
|
+
return "green"; // At target
|
|
126
110
|
}
|
|
127
111
|
if (diff > 0) {
|
|
128
|
-
return
|
|
112
|
+
return "red"; // Too hot
|
|
129
113
|
}
|
|
130
|
-
return
|
|
114
|
+
return "blue"; // Too cold
|
|
131
115
|
}
|
|
132
116
|
export function interpolateColor(value, minColor, maxColor) {
|
|
133
117
|
const clampedValue = Math.max(0, Math.min(1, value));
|
|
@@ -138,7 +122,7 @@ export function interpolateColor(value, minColor, maxColor) {
|
|
|
138
122
|
];
|
|
139
123
|
}
|
|
140
124
|
export function rgbToHex(r, g, b) {
|
|
141
|
-
return `#${[r, g, b].map((c) => c.toString(16).padStart(2,
|
|
125
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
142
126
|
}
|
|
143
127
|
export function hexToRgb(hex) {
|
|
144
128
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-mug",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A CLI app for controlling Ember mugs via Bluetooth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"prebuild": "npm run clean",
|
|
20
20
|
"start": "node dist/cli.js",
|
|
21
21
|
"dev": "tsx src/cli.tsx",
|
|
22
|
+
"dev-mocked": "EMBER_MOCK=true tsx src/cli.tsx",
|
|
22
23
|
"prepublishOnly": "npm run build",
|
|
23
24
|
"version": "npm run build"
|
|
24
25
|
},
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { RGBColor } from '../lib/types.js';
|
|
3
|
-
interface ColorControlProps {
|
|
4
|
-
color: RGBColor;
|
|
5
|
-
onColorChange: (color: RGBColor) => void;
|
|
6
|
-
isActive: boolean;
|
|
7
|
-
}
|
|
8
|
-
export declare function ColorControl({ color, onColorChange, isActive, }: ColorControlProps): React.ReactElement;
|
|
9
|
-
export {};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { rgbToHex } from '../lib/utils.js';
|
|
5
|
-
const PRESET_COLORS = [
|
|
6
|
-
{ name: 'Orange', color: { r: 255, g: 147, b: 41, a: 255 } },
|
|
7
|
-
{ name: 'Red', color: { r: 255, g: 0, b: 0, a: 255 } },
|
|
8
|
-
{ name: 'Green', color: { r: 0, g: 255, b: 0, a: 255 } },
|
|
9
|
-
{ name: 'Blue', color: { r: 0, g: 128, b: 255, a: 255 } },
|
|
10
|
-
{ name: 'Purple', color: { r: 128, g: 0, b: 255, a: 255 } },
|
|
11
|
-
{ name: 'Pink', color: { r: 255, g: 105, b: 180, a: 255 } },
|
|
12
|
-
{ name: 'White', color: { r: 255, g: 255, b: 255, a: 255 } },
|
|
13
|
-
{ name: 'Teal', color: { r: 0, g: 255, b: 200, a: 255 } },
|
|
14
|
-
];
|
|
15
|
-
export function ColorControl({ color, onColorChange, isActive, }) {
|
|
16
|
-
const [selectedChannel, setSelectedChannel] = useState('r');
|
|
17
|
-
const [customMode, setCustomMode] = useState(false);
|
|
18
|
-
useInput((input, key) => {
|
|
19
|
-
if (!isActive)
|
|
20
|
-
return;
|
|
21
|
-
// Number keys for preset colors
|
|
22
|
-
const numKey = parseInt(input, 10);
|
|
23
|
-
if (numKey >= 1 && numKey <= PRESET_COLORS.length) {
|
|
24
|
-
onColorChange(PRESET_COLORS[numKey - 1].color);
|
|
25
|
-
setCustomMode(false);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
// Toggle custom mode
|
|
29
|
-
if (input === 'c') {
|
|
30
|
-
setCustomMode(!customMode);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (customMode) {
|
|
34
|
-
// Switch channels
|
|
35
|
-
if (input === 'r') {
|
|
36
|
-
setSelectedChannel('r');
|
|
37
|
-
}
|
|
38
|
-
else if (input === 'g') {
|
|
39
|
-
setSelectedChannel('g');
|
|
40
|
-
}
|
|
41
|
-
else if (input === 'b') {
|
|
42
|
-
setSelectedChannel('b');
|
|
43
|
-
}
|
|
44
|
-
// Adjust selected channel
|
|
45
|
-
let delta = 0;
|
|
46
|
-
if (key.leftArrow || input === 'h') {
|
|
47
|
-
delta = -10;
|
|
48
|
-
}
|
|
49
|
-
else if (key.rightArrow || input === 'l') {
|
|
50
|
-
delta = 10;
|
|
51
|
-
}
|
|
52
|
-
else if (key.upArrow || input === 'k') {
|
|
53
|
-
delta = 25;
|
|
54
|
-
}
|
|
55
|
-
else if (key.downArrow || input === 'j') {
|
|
56
|
-
delta = -25;
|
|
57
|
-
}
|
|
58
|
-
if (delta !== 0) {
|
|
59
|
-
const newColor = { ...color };
|
|
60
|
-
newColor[selectedChannel] = Math.max(0, Math.min(255, newColor[selectedChannel] + delta));
|
|
61
|
-
onColorChange(newColor);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}, { isActive });
|
|
65
|
-
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { justifyContent: "center", children: _jsx(Text, { bold: true, children: "LED Color" }) }), _jsx(Box, { justifyContent: "center", marginY: 1, children: _jsxs(Text, { children: ["Current: ", _jsx(Text, { color: rgbToHex(color.r, color.g, color.b), children: "\u25CF" }), ' ', _jsxs(Text, { dimColor: true, children: ["(", rgbToHex(color.r, color.g, color.b), ")"] })] }) }), _jsx(Box, { justifyContent: "center", gap: 1, marginY: 1, children: PRESET_COLORS.map((preset, index) => (_jsxs(Box, { children: [_jsx(Text, { color: rgbToHex(preset.color.r, preset.color.g, preset.color.b), children: "\u25CF" }), _jsx(Text, { dimColor: true, children: index + 1 })] }, preset.name))) }), customMode && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "Custom Color Mode" }) }), _jsxs(Box, { justifyContent: "center", gap: 2, children: [_jsx(ChannelSlider, { label: "R", value: color.r, isSelected: selectedChannel === 'r', color: "red" }), _jsx(ChannelSlider, { label: "G", value: color.g, isSelected: selectedChannel === 'g', color: "green" }), _jsx(ChannelSlider, { label: "B", value: color.b, isSelected: selectedChannel === 'b', color: "blue" })] })] })), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Press ", _jsxs(Text, { color: "cyan", children: ["1-", PRESET_COLORS.length] }), " for presets |", ' ', _jsx(Text, { color: "cyan", children: "c" }), " for custom mode"] }) })] }));
|
|
66
|
-
}
|
|
67
|
-
function ChannelSlider({ label, value, isSelected, color, }) {
|
|
68
|
-
const width = 10;
|
|
69
|
-
const filled = Math.round((value / 255) * width);
|
|
70
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? color : 'gray', children: [label, ": ", '█'.repeat(filled), '░'.repeat(width - filled), " ", value.toString().padStart(3)] }) }));
|
|
71
|
-
}
|