mirror of
https://github.com/gangoke/kobrax-lan-hass-component.git
synced 2026-06-10 05:02:12 +02:00
Compare commits
5 Commits
v0.9.13
...
card_front
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a73fafe1f0 | ||
|
|
894f1fad22 | ||
|
|
35091826a4 | ||
|
|
f675c8f456 | ||
|
|
a65b58798c |
69
.github/workflows/build-card.yml
vendored
Normal file
69
.github/workflows/build-card.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Build Kobrax Card
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- custom_components/kobrax_lan/frontend_panel/**
|
||||
- .github/workflows/build-card.yml
|
||||
|
||||
jobs:
|
||||
build-card:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: custom_components/kobrax_lan/frontend_panel
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
cache-dependency-path: custom_components/kobrax_lan/frontend_panel/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build card
|
||||
run: npm run build_card:quick
|
||||
|
||||
- name: Upload card artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: kobrax-lan-card
|
||||
path: custom_components/kobrax_lan/frontend_panel/dist/kobrax-lan-card.js
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Dispatch card repo sync
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
CARD_REPO_DISPATCH_TOKEN: ${{ secrets.CARD_REPO_DISPATCH_TOKEN }}
|
||||
CARD_REPO_OWNER: ${{ github.repository_owner }}
|
||||
CARD_REPO_NAME: kobrax-lan-hass-card
|
||||
if: ${{ env.CARD_REPO_DISPATCH_TOKEN != '' }}
|
||||
with:
|
||||
script: |
|
||||
const token = process.env.CARD_REPO_DISPATCH_TOKEN;
|
||||
const owner = process.env.CARD_REPO_OWNER;
|
||||
const repo = process.env.CARD_REPO_NAME;
|
||||
|
||||
if (!token) {
|
||||
core.info('CARD_REPO_DISPATCH_TOKEN is not set; skipping card repo dispatch.');
|
||||
return;
|
||||
}
|
||||
|
||||
const octokit = github.getOctokit(token);
|
||||
await octokit.request('POST /repos/{owner}/{repo}/dispatches', {
|
||||
owner,
|
||||
repo,
|
||||
event_type: 'sync-card',
|
||||
client_payload: {
|
||||
source_repository: `${context.repo.owner}/${context.repo.repo}`,
|
||||
run_id: context.runId,
|
||||
artifact_name: 'kobrax-lan-card',
|
||||
source_branch: context.ref.replace('refs/heads/', ''),
|
||||
},
|
||||
});
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -12,9 +12,11 @@ Architecture:
|
||||
- Core printer sensors (state, temperatures, progress, file, layer/time data)
|
||||
- Light control
|
||||
- Print speed mode selection
|
||||
- Service calls for print speed mode and target temperatures (nozzle/bed)
|
||||
- Printer action buttons (pause, resume, cancel)
|
||||
- Camera stream entity using the printer RTSP URL from KX-Bridge, with bridge MJPEG proxy fallback
|
||||
- Camera snapshot fallback using `/api/camera/snapshot`
|
||||
- Card-compat alias entities for `kobrax-lan-card` (for example `job_state`, `job_progress`, `printer_online`, `target_nozzle_temperature`)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -40,3 +42,21 @@ The config flow asks for:
|
||||
- This integration talks to KX-Bridge HTTP endpoints and does not connect directly to the printer.
|
||||
- Keep KX-Bridge and Home Assistant on the same trusted network.
|
||||
- Native WebRTC is not implemented in this integration. If you want WebRTC in Home Assistant, point `go2rtc` or a WebRTC-capable HA add-on at the camera entity's RTSP source.
|
||||
|
||||
## Frontend Card Source
|
||||
|
||||
The source-of-truth for the Kobrax LAN card is in:
|
||||
|
||||
- custom_components/kobrax_lan/frontend_panel
|
||||
|
||||
Build output from that folder creates:
|
||||
|
||||
- dist/kobrax-lan-card.js
|
||||
|
||||
The separate `kobrax-lan-hass-card` repository is the HACS distribution repo for the built artifact.
|
||||
|
||||
Automation notes:
|
||||
|
||||
- The build workflow uploads `kobrax-lan-card.js` as a GitHub Actions artifact.
|
||||
- If you add a `CARD_REPO_DISPATCH_TOKEN` secret, the workflow also dispatches a sync event to the card repo.
|
||||
- The card repo workflow expects a `SOURCE_REPO_TOKEN` secret so it can download the artifact from this repo and commit the updated `kobrax-lan-card.js` file.
|
||||
|
||||
@@ -9,6 +9,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from .api import KobraXApiClient
|
||||
from .const import CONF_HOST, DOMAIN, PLATFORMS
|
||||
from .coordinator import KobraXCoordinator
|
||||
from .services import async_register_services, async_unregister_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -36,6 +37,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"entry": entry,
|
||||
}
|
||||
|
||||
async_register_services(hass)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
@@ -44,4 +47,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
if not any(isinstance(value, dict) and "api" in value for value in hass.data[DOMAIN].values()):
|
||||
async_unregister_services(hass)
|
||||
return unload_ok
|
||||
|
||||
@@ -36,6 +36,12 @@ BINARY_SENSORS: tuple[KobraXBinaryDescription, ...] = (
|
||||
value_key="light_on",
|
||||
icon="mdi:lightbulb",
|
||||
),
|
||||
KobraXBinaryDescription(
|
||||
key="printer_online",
|
||||
name="Printer Online",
|
||||
value_key="kobra_state",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
138
custom_components/kobrax_lan/frontend_panel/.eslintrc.js
Normal file
138
custom_components/kobrax_lan/frontend_panel/.eslintrc.js
Normal file
@@ -0,0 +1,138 @@
|
||||
module.exports = {
|
||||
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended-type-checked",
|
||||
"plugin:@typescript-eslint/strict-type-checked",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"plugin:import/recommended",
|
||||
],
|
||||
plugins: ["prettier", "lit", "import"],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: "module", // Allows for the use of imports
|
||||
experimentalDecorators: true,
|
||||
emitDecoratorMetadata: true,
|
||||
projectService: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"alwaysTryTypes": true,
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
},
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts", ".tsx"]
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/consistent-generic-constructors": "error",
|
||||
"@typescript-eslint/consistent-type-exports": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
"@typescript-eslint/no-confusing-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-dupe-class-members": "error",
|
||||
"@typescript-eslint/no-shadow": "error",
|
||||
"@typescript-eslint/no-unnecessary-parameter-property-assignment": "error",
|
||||
"@typescript-eslint/no-unused-expressions": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-use-before-define": "error",
|
||||
"@typescript-eslint/parameter-properties": "error",
|
||||
"@typescript-eslint/restrict-template-expressions": [
|
||||
"error",
|
||||
{
|
||||
"allowNumber": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/typedef": "error",
|
||||
"curly": "error",
|
||||
"eqeqeq": "error",
|
||||
"lit/attribute-names": "warn",
|
||||
"lit/ban-attributes": "error",
|
||||
"lit/lifecycle-super": "error",
|
||||
"lit/no-classfield-shadowing": "error",
|
||||
"lit/no-invalid-escape-sequences": "error",
|
||||
"lit/no-legacy-imports": "error",
|
||||
"lit/no-native-attributes": "error",
|
||||
"lit/no-private-properties": "error",
|
||||
"lit/no-template-arrow": "error",
|
||||
"lit/no-template-bind": "error",
|
||||
"lit/no-template-map": "error",
|
||||
"lit/no-this-assign-in-render": "error",
|
||||
"lit/no-useless-template-literals": "error",
|
||||
"lit/no-value-attribute": "error",
|
||||
"lit/prefer-nothing": "error",
|
||||
"lit/prefer-static-styles": "error",
|
||||
"lit/quoted-expressions": "error",
|
||||
"lit/value-after-constraints": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
"groups": [
|
||||
"external",
|
||||
"builtin",
|
||||
"internal",
|
||||
"sibling",
|
||||
"parent",
|
||||
"index"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-console": "warn",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-empty-function": "warn",
|
||||
"no-undef": "error",
|
||||
"no-unneeded-ternary": "warn",
|
||||
"no-var": "error",
|
||||
"operator-assignment": "warn",
|
||||
"prefer-const": "error",
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
"ignoreCase": false,
|
||||
"ignoreDeclarationSort": true,
|
||||
"ignoreMemberSort": false
|
||||
}
|
||||
]
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
"*.ts",
|
||||
"**/*.ts"
|
||||
],
|
||||
excludedFiles: [
|
||||
"./src/lib",
|
||||
"./src/lib/**/*.js",
|
||||
]
|
||||
},
|
||||
],
|
||||
globals: {
|
||||
customElements: "writable",
|
||||
document: "writable",
|
||||
history: "writable",
|
||||
window: "writable",
|
||||
clearInterval: "readonly",
|
||||
setInterval: "readonly",
|
||||
clearTimeout: "readonly",
|
||||
setTimeout: "readonly",
|
||||
CustomEvent: "readonly",
|
||||
HTMLElement: "readonly",
|
||||
Window: "readonly",
|
||||
Event: "readonly",
|
||||
FillMode: "readonly",
|
||||
scrollTo: "readonly"
|
||||
}
|
||||
};
|
||||
2
custom_components/kobrax_lan/frontend_panel/.gitignore
vendored
Normal file
2
custom_components/kobrax_lan/frontend_panel/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
25
custom_components/kobrax_lan/frontend_panel/.prettierrc
Normal file
25
custom_components/kobrax_lan/frontend_panel/.prettierrc
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.ts,*.js",
|
||||
"options": {
|
||||
"htmlWhitespaceSensitivity": "strict"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.scss",
|
||||
"options": {
|
||||
"parser": "scss",
|
||||
"singleQuote": true,
|
||||
"printWidth": 200
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.md",
|
||||
"options": {
|
||||
"printWidth": 200
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
38
custom_components/kobrax_lan/frontend_panel/README.md
Normal file
38
custom_components/kobrax_lan/frontend_panel/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Kobrax LAN Frontend Panel Source
|
||||
|
||||
This folder is the source-of-truth for the Kobrax LAN Lovelace card build.
|
||||
|
||||
## Build Card
|
||||
|
||||
From this folder:
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build_card:quick
|
||||
```
|
||||
|
||||
Build output:
|
||||
|
||||
- dist/kobrax-lan-card.js
|
||||
|
||||
## Export To Card Repo
|
||||
|
||||
After building, copy the artifact to the HACS card distribution repo:
|
||||
|
||||
- Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
./scripts/export-card-to-repo.ps1
|
||||
```
|
||||
|
||||
- Linux/macOS:
|
||||
|
||||
```bash
|
||||
./scripts/export-card-to-repo.sh
|
||||
```
|
||||
|
||||
Default export target:
|
||||
|
||||
- ../../../../kobrax-lan-hass-card/kobrax-lan-card.js
|
||||
|
||||
Override export target by setting CARD_REPO_PATH.
|
||||
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"title": "Anycubic Cloud",
|
||||
"common": {
|
||||
"actions": {
|
||||
"cancel": "Cancel",
|
||||
"pause": "Pause",
|
||||
"print": "Print",
|
||||
"resume": "Resume",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"save": "Save"
|
||||
},
|
||||
"messages": {
|
||||
"mqtt_unsupported": "This feature requires MQTT to retrieve data but unfortunately MQTT is not supported with the configured authentication mode."
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"buttons": {
|
||||
"print_settings": "Print Settings",
|
||||
"dry": "Dry",
|
||||
"runout_refill": "Refill"
|
||||
},
|
||||
"configure": {
|
||||
"tabs": {
|
||||
"main": "Main",
|
||||
"stats": "Stats",
|
||||
"colours": "ACE Colour Presets"
|
||||
},
|
||||
"labels": {
|
||||
"printer_id": "Select Printer",
|
||||
"vertical": "Vertical Layout?",
|
||||
"round": "Round Stats?",
|
||||
"use_24hr": "Use 24hr Time?",
|
||||
"show_settings_button": "Always show print settings button?",
|
||||
"always_show": "Always show card?",
|
||||
"temperature_unit": "Temperature Unit",
|
||||
"light_entity_id": "Light Entity",
|
||||
"power_entity_id": "Power Entity",
|
||||
"camera_entity_id": "Camera Entity",
|
||||
"scale_factor": "Scale Factor",
|
||||
"slot_colors": "Slot Colour Presets"
|
||||
}
|
||||
},
|
||||
"print_settings": {
|
||||
"confirm_message": "Are you sure you want to {action} the print?",
|
||||
"label_nozzle_temp": "Nozzle Temperature",
|
||||
"label_hotbed_temp": "Hotbed Temperature",
|
||||
"label_fan_speed": "Fan Speed",
|
||||
"label_aux_fan_speed": "AUX Fan Speed",
|
||||
"label_box_fan_speed": "Box Fan Speed",
|
||||
"print_pause": "Pause Print",
|
||||
"print_resume": "Resume Print",
|
||||
"print_cancel": "Cancel Print",
|
||||
"save_speed_mode": "Save Speed Mode",
|
||||
"save_target_nozzle": "Save Target Nozzle",
|
||||
"save_target_hotbed": "Save Target Hotbed",
|
||||
"save_fan_speed": "Save Fan Speed",
|
||||
"save_aux_fan_speed": "Save AUX Fan Speed",
|
||||
"save_box_fan_speed": "Save Box Fan Speed"
|
||||
},
|
||||
"drying_settings": {
|
||||
"heading": "Drying Options",
|
||||
"button_preset": "Preset",
|
||||
"button_stop_drying": "Stop Drying",
|
||||
"button_minutes": "Mins"
|
||||
},
|
||||
"spool_settings": {
|
||||
"heading": "Editing Slot",
|
||||
"label_select_material": "Select Material",
|
||||
"label_select_colour": "Manually select colour"
|
||||
},
|
||||
"monitored_stats": {
|
||||
"ETA": "ETA",
|
||||
"Elapsed": "Elapsed",
|
||||
"Remaining": "Remaining",
|
||||
"Status": "Status",
|
||||
"Online": "Online",
|
||||
"Availability": "Availability",
|
||||
"Project": "Project",
|
||||
"Layer": "Layer",
|
||||
"Hotend": "Hotend",
|
||||
"Bed": "Bed",
|
||||
"T Hotend": "T Hotend",
|
||||
"T Bed": "T Bed",
|
||||
"Dry Status": "Dry Status",
|
||||
"Dry Time": "Dry Time",
|
||||
"Speed Mode": "Speed Mode",
|
||||
"Fan Speed": "Fan Speed",
|
||||
"Dry Status": "Dry Status",
|
||||
"Dry Time": "Dry Time",
|
||||
"On Time": "On Time",
|
||||
"Off Time": "Off Time",
|
||||
"Bottom Time": "Bottom Time",
|
||||
"Model Height": "Model Height",
|
||||
"Bottom Layers": "Bottom Layers",
|
||||
"Z Up Height": "Z Up Height",
|
||||
"Z Up Speed": "Z Up Speed",
|
||||
"Z Down Speed": "Z Down Speed"
|
||||
}
|
||||
},
|
||||
"panels": {
|
||||
"initial": {
|
||||
"printer_select": "Select a printer."
|
||||
},
|
||||
"main": {
|
||||
"title": "Main",
|
||||
"cards": {
|
||||
"main": {
|
||||
"description": "General information about the printer.",
|
||||
"fields": {
|
||||
"printer_name": "Name",
|
||||
"printer_id": "ID",
|
||||
"printer_mac": "MAC",
|
||||
"printer_model": "Model",
|
||||
"printer_fw_version": "FW Version",
|
||||
"printer_fw_update_available": "FW Status",
|
||||
"printer_online": "Online",
|
||||
"printer_available": "Available",
|
||||
"curr_nozzle_temp": "Current Nozzle Temperature",
|
||||
"curr_hotbed_temp": "Current Hotbed Temperature",
|
||||
"target_nozzle_temp": "Target Nozzle Temperature",
|
||||
"target_hotbed_temp": "Target Hotbed Temperature",
|
||||
"job_state": "Job State",
|
||||
"job_progress": "Job Progress",
|
||||
"ace_fw_version": "ACE FW Version",
|
||||
"ace_fw_update_available": "ACE FW Status",
|
||||
"drying_active": "ACE Drying Status",
|
||||
"drying_progress": "ACE Drying Progress"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files_cloud": {
|
||||
"title": "Cloud Files",
|
||||
"cards": {}
|
||||
},
|
||||
"files_local": {
|
||||
"title": "Local Files",
|
||||
"cards": {}
|
||||
},
|
||||
"files_udisk": {
|
||||
"title": "USB Files",
|
||||
"cards": {}
|
||||
},
|
||||
"print_save_in_cloud": {
|
||||
"title": "Print (Save in user cloud)",
|
||||
"cards": {}
|
||||
},
|
||||
"print_no_cloud_save": {
|
||||
"title": "Print (No Cloud Save)",
|
||||
"cards": {}
|
||||
},
|
||||
"debug": {
|
||||
"title": "Debug",
|
||||
"cards": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import * as en from './languages/en.json';
|
||||
|
||||
import IntlMessageFormat from 'intl-messageformat';
|
||||
|
||||
var languages: any = {
|
||||
en: en,
|
||||
};
|
||||
|
||||
export function localize(string: string, language: string, ...args: any[]): string {
|
||||
const lang = language.replace(/['"]+/g, '');
|
||||
|
||||
var translated: string;
|
||||
|
||||
try {
|
||||
translated = string.split('.').reduce((o, i) => o[i], languages[lang]);
|
||||
} catch (e) {
|
||||
translated = string.split('.').reduce((o, i) => o[i], languages['en']);
|
||||
}
|
||||
|
||||
if (translated === undefined) translated = string.split('.').reduce((o, i) => o[i], languages['en']);
|
||||
|
||||
if (!args.length) return translated;
|
||||
|
||||
const argObject = {};
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
let key = args[i];
|
||||
key = key.replace(/^{([^}]+)?}$/, '$1');
|
||||
argObject[key] = args[i + 1];
|
||||
}
|
||||
|
||||
try {
|
||||
const message = new IntlMessageFormat(translated, language);
|
||||
return message.format(argObject) as string;
|
||||
} catch (err) {
|
||||
return 'Translation ' + err;
|
||||
}
|
||||
}
|
||||
4801
custom_components/kobrax_lan/frontend_panel/package-lock.json
generated
Normal file
4801
custom_components/kobrax_lan/frontend_panel/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
custom_components/kobrax_lan/frontend_panel/package.json
Normal file
57
custom_components/kobrax_lan/frontend_panel/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "kobrax-lan-frontend-panel",
|
||||
"version": "0.2.2",
|
||||
"description": "kobrax lan frontend panel and card source",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"check-types": "tsc --noemit",
|
||||
"build": "npm run lint && npm run rollup && npm run babel",
|
||||
"build:quick": "npm run rollup && npm run babel",
|
||||
"build_card": "npm run lint && npm run rollup_card && npm run babel_card",
|
||||
"build_card:quick": "npm run rollup_card && npm run babel_card",
|
||||
"rollup": "rollup -c",
|
||||
"rollup_card": "rollup -c rollup.config-card.mjs",
|
||||
"babel": "npx babel dist/anycubic-cloud-panel.js --out-file dist/anycubic-cloud-panel.js",
|
||||
"babel_card": "npx babel dist/kobrax-lan-card.js --out-file dist/kobrax-lan-card.js",
|
||||
"eslint": "eslint src --fix -c .eslintrc.js --ignore-pattern src/lib",
|
||||
"lint": "npm run eslint && npm run check-types",
|
||||
"prettier": "prettier src/components/**/*.ts --write",
|
||||
"start": "rollup -c --watch"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/cli": "^7.24.8",
|
||||
"@babel/core": "^7.24.9",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||
"@babel/plugin-transform-class-properties": "^7.24.7",
|
||||
"@date-fns/utc": "^2.1.0",
|
||||
"@eslint/js": "^9.7.0",
|
||||
"@lit-labs/motion": "^1.0.7",
|
||||
"@lit-labs/observers": "^2.0.2",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-lit": "^1.14.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"home-assistant-js-websocket": "^9.4.0",
|
||||
"intl-messageformat": "^10.5.14",
|
||||
"lit": "^3.1.4",
|
||||
"modern-color": "^1.1.3",
|
||||
"prettier": "^3.3.3",
|
||||
"rollup": "^2.79.2",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^7.17.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs({
|
||||
include: 'node_modules/**'
|
||||
}),
|
||||
typescript(),
|
||||
json(),
|
||||
babel(),
|
||||
terser({
|
||||
ecma: 2021,
|
||||
module: true,
|
||||
warnings: true,
|
||||
}),
|
||||
],
|
||||
input: 'src/components/printer_card/printer_card.ts',
|
||||
output: {
|
||||
file: 'dist/kobrax-lan-card.js',
|
||||
format: 'iife',
|
||||
sourcemap: false
|
||||
},
|
||||
context: 'window',
|
||||
preserveEntrySignatures: 'strict',
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs({
|
||||
include: 'node_modules/**'
|
||||
}),
|
||||
typescript(),
|
||||
json(),
|
||||
babel(),
|
||||
terser({
|
||||
ecma: 2021,
|
||||
module: true,
|
||||
warnings: true,
|
||||
}),
|
||||
],
|
||||
input: 'src/anycubic-cloud-panel.ts',
|
||||
output: {
|
||||
dir: 'dist',
|
||||
format: 'iife',
|
||||
sourcemap: false
|
||||
},
|
||||
context: 'window',
|
||||
preserveEntrySignatures: 'strict',
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
Param(
|
||||
[string]$CardRepoPath = $env:CARD_REPO_PATH
|
||||
)
|
||||
|
||||
if (-not $CardRepoPath) {
|
||||
$CardRepoPath = "../../../../kobrax-lan-hass-card"
|
||||
}
|
||||
|
||||
$source = Join-Path $PSScriptRoot "../dist/kobrax-lan-card.js"
|
||||
$targetDir = Resolve-Path -Path $CardRepoPath -ErrorAction SilentlyContinue
|
||||
if (-not $targetDir) {
|
||||
throw "Card repo path not found: $CardRepoPath"
|
||||
}
|
||||
$target = Join-Path $targetDir.Path "kobrax-lan-card.js"
|
||||
|
||||
if (-not (Test-Path $source)) {
|
||||
throw "Build artifact not found: $source"
|
||||
}
|
||||
|
||||
Copy-Item -Path $source -Destination $target -Force
|
||||
Write-Host "Exported card artifact to $target"
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
CARD_REPO_PATH="${CARD_REPO_PATH:-../../../../kobrax-lan-hass-card}"
|
||||
SOURCE="$(cd "$(dirname "$0")/.." && pwd)/dist/kobrax-lan-card.js"
|
||||
TARGET_DIR="$(cd "$CARD_REPO_PATH" && pwd)"
|
||||
TARGET="$TARGET_DIR/kobrax-lan-card.js"
|
||||
|
||||
if [[ ! -f "$SOURCE" ]]; then
|
||||
echo "Build artifact not found: $SOURCE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$SOURCE" "$TARGET"
|
||||
echo "Exported card artifact to $TARGET"
|
||||
@@ -0,0 +1,451 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import "./views/debug/view-debug.ts";
|
||||
import "./views/main/view-main.ts";
|
||||
import "./views/files/view-files_cloud.ts";
|
||||
import "./views/files/view-files_local.ts";
|
||||
import "./views/files/view-files_udisk.ts";
|
||||
import "./views/print/view-print-no_cloud_save.ts";
|
||||
import "./views/print/view-print-save_in_cloud.ts";
|
||||
|
||||
import { DEBUG } from "./const";
|
||||
import { HASSDomEvent } from "./fire_event";
|
||||
import {
|
||||
getPage,
|
||||
getPrinterDevID,
|
||||
getPrinterDevices,
|
||||
getSelectedPrinter,
|
||||
navigateToPage,
|
||||
navigateToPrinter,
|
||||
} from "./helpers";
|
||||
import {
|
||||
DomClickEvent,
|
||||
EvtTargPrinterDevId,
|
||||
HassDevice,
|
||||
HassDeviceList,
|
||||
HassPanel,
|
||||
HassRoute,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
PageChangeDetail,
|
||||
} from "./types";
|
||||
import * as pkgjson from "../package.json";
|
||||
import { localize } from "../localize/localize";
|
||||
|
||||
window.console.info(
|
||||
`%c ANYCUBIC-PANEL %c v${pkgjson.version} `,
|
||||
"color: orange; font-weight: bold; background: black",
|
||||
"color: white; font-weight: bold; background: dimgray",
|
||||
);
|
||||
|
||||
@customElement("anycubic-cloud-panel")
|
||||
export class AnycubicCloudPanel extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property()
|
||||
public route!: HassRoute;
|
||||
|
||||
@property()
|
||||
public panel!: HassPanel;
|
||||
|
||||
@state()
|
||||
private printers?: HassDeviceList;
|
||||
|
||||
@state()
|
||||
private selectedPage: string = "main";
|
||||
|
||||
@state()
|
||||
private selectedPrinterID: string | undefined;
|
||||
|
||||
@state()
|
||||
private selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@state()
|
||||
private language: string;
|
||||
|
||||
@state()
|
||||
private _tabMain: string;
|
||||
|
||||
@state()
|
||||
private _tabFilesLocal: string;
|
||||
|
||||
@state()
|
||||
private _tabFilesUdisk: string;
|
||||
|
||||
@state()
|
||||
private _tabFilesCloud: string;
|
||||
|
||||
@state()
|
||||
private _tabPrintNoSave: string;
|
||||
|
||||
@state()
|
||||
private _tabPrintSave: string;
|
||||
|
||||
@state()
|
||||
private _tabDebug: string;
|
||||
|
||||
@state()
|
||||
private _mainTitle: string;
|
||||
|
||||
@state()
|
||||
private _selectPrinter: string;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this._handleLocationChange);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
window.removeEventListener("location-changed", this._handleLocationChange);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
private _handleLocationChange = (): void => {
|
||||
if (!window.location.pathname.includes("anycubic-cloud")) {
|
||||
return;
|
||||
}
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("hass") && this.hass.language !== this.language) {
|
||||
this.language = this.hass.language;
|
||||
this._tabMain = localize("panels.main.title", this.language);
|
||||
this._tabFilesLocal = localize("panels.files_local.title", this.language);
|
||||
this._tabFilesUdisk = localize("panels.files_udisk.title", this.language);
|
||||
this._tabFilesCloud = localize("panels.files_cloud.title", this.language);
|
||||
this._tabPrintNoSave = localize(
|
||||
"panels.print_no_cloud_save.title",
|
||||
this.language,
|
||||
);
|
||||
this._tabPrintSave = localize(
|
||||
"panels.print_save_in_cloud.title",
|
||||
this.language,
|
||||
);
|
||||
this._tabDebug = localize("panels.debug.title", this.language);
|
||||
this._mainTitle = localize("title", this.language);
|
||||
this._selectPrinter = localize(
|
||||
"panels.initial.printer_select",
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
this.printers = getPrinterDevices(this.hass);
|
||||
this.selectedPage = getPage(this.route);
|
||||
this.selectedPrinterID = getPrinterDevID(this.route);
|
||||
this.selectedPrinterDevice = getSelectedPrinter(
|
||||
this.printers,
|
||||
this.selectedPrinterID,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return this.getInitialView();
|
||||
}
|
||||
|
||||
renderPrinterPage(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="header">
|
||||
${this.renderToolbar()}
|
||||
<ha-tabs
|
||||
scrollable
|
||||
attr-for-selected="page-name"
|
||||
.selected=${this.selectedPage}
|
||||
@iron-activate=${this.handlePageSelected}
|
||||
>
|
||||
<paper-tab page-name="main"> ${this._tabMain} </paper-tab>
|
||||
<paper-tab page-name="local-files">
|
||||
${this._tabFilesLocal}
|
||||
</paper-tab>
|
||||
<paper-tab page-name="udisk-files">
|
||||
${this._tabFilesUdisk}
|
||||
</paper-tab>
|
||||
<paper-tab page-name="cloud-files">
|
||||
${this._tabFilesCloud}
|
||||
</paper-tab>
|
||||
<paper-tab page-name="print-no_cloud_save">
|
||||
${this._tabPrintNoSave}
|
||||
</paper-tab>
|
||||
<paper-tab page-name="print-save_in_cloud">
|
||||
${this._tabPrintSave}
|
||||
</paper-tab>
|
||||
${DEBUG // eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
||||
? html`
|
||||
<paper-tab page-name="debug"> ${this._tabDebug} </paper-tab>
|
||||
`
|
||||
: null}
|
||||
</ha-tabs>
|
||||
</div>
|
||||
<div class="view">${this.getView(this.route)}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderToolbar(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
<ha-menu-button
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div class="main-title">${this._mainTitle}</div>
|
||||
<div class="version">v${pkgjson.version}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getInitialView(): LitTemplateResult {
|
||||
if (this.selectedPrinterID) {
|
||||
return this.renderPrinterPage();
|
||||
} else {
|
||||
return html`
|
||||
<div class="header">${this.renderToolbar()}</div>
|
||||
<printer-select elevation="2">
|
||||
<p>${this._selectPrinter}</p>
|
||||
<ul class="printers-container">
|
||||
${this.printers
|
||||
? Object.keys(this.printers).map(
|
||||
(printerID) =>
|
||||
html`<li
|
||||
class="printer-select-box"
|
||||
.printer_id=${printerID}
|
||||
@click=${this._handlePrinterClick}
|
||||
>
|
||||
${this.printers ? this.printers[printerID].name : ""}
|
||||
</li>`,
|
||||
)
|
||||
: null}
|
||||
</ul>
|
||||
</printer-select>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
getView(route: HassRoute): LitTemplateResult {
|
||||
switch (this.selectedPage) {
|
||||
case "local-files":
|
||||
return html`
|
||||
<anycubic-view-files_local
|
||||
class="ac_wide_view"
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-files_local>
|
||||
`;
|
||||
case "udisk-files":
|
||||
return html`
|
||||
<anycubic-view-files_udisk
|
||||
class="ac_wide_view"
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-files_udisk>
|
||||
`;
|
||||
case "cloud-files":
|
||||
return html`
|
||||
<anycubic-view-files_cloud
|
||||
class="ac_wide_view"
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-files_cloud>
|
||||
`;
|
||||
case "print-no_cloud_save":
|
||||
return html`
|
||||
<anycubic-view-print-no_cloud_save
|
||||
class="ac_wide_view"
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-print-no_cloud_save>
|
||||
`;
|
||||
case "print-save_in_cloud":
|
||||
return html`
|
||||
<anycubic-view-print-save_in_cloud
|
||||
class="ac_wide_view"
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-print-save_in_cloud>
|
||||
`;
|
||||
case "main":
|
||||
return html`
|
||||
<anycubic-view-main
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-main>
|
||||
`;
|
||||
case "debug":
|
||||
return html`
|
||||
<anycubic-view-debug
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.panel=${this.panel}
|
||||
.printers=${this.printers}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
></anycubic-view-debug>
|
||||
`;
|
||||
default:
|
||||
return html`
|
||||
<ha-card header="Page not found">
|
||||
<div class="card-content">
|
||||
The page you are trying to reach cannot be found. Please select a
|
||||
page from the menu above to continue.
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
_handlePrinterClick = (ev: DomClickEvent<EvtTargPrinterDevId>): void => {
|
||||
navigateToPrinter(this, ev.currentTarget.printer_id);
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
handlePageSelected = (ev: HASSDomEvent<PageChangeDetail>): void => {
|
||||
const newPage = ev.detail.item.getAttribute("page-name") as string;
|
||||
if (newPage !== getPage(this.route)) {
|
||||
navigateToPage(this, newPage);
|
||||
this.requestUpdate();
|
||||
} else {
|
||||
scrollTo(0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
}
|
||||
.header {
|
||||
background-color: var(--app-header-background-color);
|
||||
color: var(--app-header-text-color, white);
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
}
|
||||
.toolbar {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
padding: 0 16px;
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.main-title {
|
||||
margin: 0 0 0 24px;
|
||||
line-height: 20px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
ha-tabs {
|
||||
margin-left: max(env(safe-area-inset-left), 24px);
|
||||
margin-right: max(env(safe-area-inset-right), 24px);
|
||||
--paper-tabs-selection-bar-color: var(
|
||||
--app-header-selection-bar-color,
|
||||
var(--app-header-text-color, #fff)
|
||||
);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.version {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: rgba(var(--rgb-text-primary-color), 0.9);
|
||||
}
|
||||
|
||||
printer-select {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.view {
|
||||
height: calc(100vh - 112px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.view > * {
|
||||
min-width: 600px;
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.view > *:last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ac_wide_view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.printers-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.printer-select-box {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
min-height: 60px;
|
||||
min-width: 250px;
|
||||
border: 2px solid #ccc3;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.printer-select-box:hover {
|
||||
background-color: #ccc3;
|
||||
border-color: #ccc9;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.view > * {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
import { buildCameraUrlFromEntity } from "../../../helpers";
|
||||
import { HassEntity, LitTemplateResult } from "../../../types";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-camera_view")
|
||||
export class AnycubicPrintercardCameraview extends LitElement {
|
||||
@property({ attribute: "show-video" })
|
||||
public showVideo?: boolean | undefined;
|
||||
|
||||
@property({ attribute: "toggle-video" })
|
||||
public toggleVideo?: () => void;
|
||||
|
||||
@property({ attribute: "camera-entity" })
|
||||
public cameraEntity: HassEntity | undefined;
|
||||
|
||||
@state()
|
||||
private camImgString: string = "none";
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
changedProperties.has("showVideo") ||
|
||||
changedProperties.has("cameraEntity")
|
||||
) {
|
||||
this.camImgString =
|
||||
this.showVideo && !!this.cameraEntity
|
||||
? `url('${buildCameraUrlFromEntity(this.cameraEntity)}')`
|
||||
: "none";
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesView = {
|
||||
display: this.showVideo ? "block" : "none",
|
||||
};
|
||||
return html`
|
||||
<div
|
||||
class="ac-printercard-cameraview"
|
||||
style=${styleMap(stylesView)}
|
||||
@click=${this._handleToggleClick}
|
||||
>
|
||||
${this.showVideo ? this._renderInner() : nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderInner(): LitTemplateResult {
|
||||
const stylesCamera = {
|
||||
"background-image": this.camImgString,
|
||||
};
|
||||
|
||||
return html` <div
|
||||
class="ac-camera-wrapper"
|
||||
style=${styleMap(stylesCamera)}
|
||||
></div>`;
|
||||
}
|
||||
|
||||
private _handleToggleClick = (): void => {
|
||||
if (this.toggleVideo) {
|
||||
this.toggleVideo();
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ac-printercard-cameraview {
|
||||
background-color: black;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ac-camera-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,762 @@
|
||||
import { mdiCog, mdiLightbulbOff, mdiLightbulbOn, mdiPower } from "@mdi/js";
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { query } from "lit/decorators/query.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { animate, Options as motionOptions } from "@lit-labs/motion";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import { fireEvent } from "../../../fire_event";
|
||||
|
||||
import {
|
||||
HassDevice,
|
||||
HassEntity,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
PrinterCardStatType,
|
||||
TemperatureUnit,
|
||||
} from "../../../types";
|
||||
|
||||
import {
|
||||
getDefaultMonitoredStats,
|
||||
getEntityState,
|
||||
getEntityStateBinary,
|
||||
getPrinterEntities,
|
||||
getPrinterEntityIdPart,
|
||||
getPrinterSensorStateObj,
|
||||
isPrintStatePrinting,
|
||||
printStateStatusColor,
|
||||
undefinedDefault,
|
||||
} from "../../../helpers";
|
||||
|
||||
import "../camera_view/camera_view.ts";
|
||||
import "../multicolorbox_view/multicolorbox_view.ts";
|
||||
import "../printer_view/printer_view.ts";
|
||||
import "../stats/stats_component.ts";
|
||||
import "../multicolorbox_view/multicolorbox_modal_drying.ts";
|
||||
import "../multicolorbox_view/multicolorbox_modal_spool.ts";
|
||||
import "../printsettings/printsettings_modal.ts";
|
||||
|
||||
const animOptionsCard: motionOptions = {
|
||||
keyframeOptions: {
|
||||
duration: 250,
|
||||
direction: "normal",
|
||||
easing: "ease-in-out",
|
||||
},
|
||||
properties: ["height", "opacity", "scale"],
|
||||
};
|
||||
|
||||
const defaultMonitoredStats: PrinterCardStatType[] = getDefaultMonitoredStats();
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-card")
|
||||
export class AnycubicPrintercardCard extends LitElement {
|
||||
@query(".ac-printer-card")
|
||||
private _printerCardContainer!: HTMLElement | Window;
|
||||
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "monitored-stats" })
|
||||
public monitoredStats?: PrinterCardStatType[] = defaultMonitoredStats;
|
||||
|
||||
@property({ attribute: "selected-printer-id" })
|
||||
public selectedPrinterID: string | undefined;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public round?: boolean = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public use_24hr?: boolean;
|
||||
|
||||
@property({ attribute: "show-settings-button", type: Boolean })
|
||||
public showSettingsButton?: boolean;
|
||||
|
||||
@property({ attribute: "always-show", type: Boolean })
|
||||
public alwaysShow?: boolean;
|
||||
|
||||
@property({ attribute: "temperature-unit", type: String })
|
||||
public temperatureUnit: TemperatureUnit = TemperatureUnit.C;
|
||||
|
||||
@property({ attribute: "light-entity-id", type: String })
|
||||
public lightEntityId?: string;
|
||||
|
||||
@property({ attribute: "power-entity-id", type: String })
|
||||
public powerEntityId?: string;
|
||||
|
||||
@property({ attribute: "camera-entity-id", type: String })
|
||||
public cameraEntityId?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public vertical?: boolean;
|
||||
|
||||
@property({ attribute: "scale-factor" })
|
||||
public scaleFactor?: number;
|
||||
|
||||
@property({ attribute: "slot-colors" })
|
||||
public slotColors?: string[];
|
||||
|
||||
@state()
|
||||
private _showVideo: boolean = false;
|
||||
|
||||
@state()
|
||||
private cameraEntityState: HassEntity | undefined = undefined;
|
||||
|
||||
@state()
|
||||
private isHidden: boolean = false;
|
||||
|
||||
@state()
|
||||
private isPrinting: boolean = false;
|
||||
|
||||
@state()
|
||||
private hiddenOverride: boolean = false;
|
||||
|
||||
@state()
|
||||
private hasColorbox: boolean = false;
|
||||
|
||||
@state()
|
||||
private hasSecondaryColorbox: boolean = false;
|
||||
|
||||
@state()
|
||||
private lightIsOn: boolean = false;
|
||||
|
||||
@state()
|
||||
private statusColor: string = "#ffc107";
|
||||
|
||||
@state()
|
||||
private printerEntities: HassEntityInfos;
|
||||
|
||||
@state()
|
||||
private printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
private progressPercent: number = 0;
|
||||
|
||||
@state()
|
||||
private _buttonPrintSettings: string;
|
||||
|
||||
@state()
|
||||
private _togglingLight: boolean = false;
|
||||
|
||||
@state()
|
||||
private _togglingPower: boolean = false;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._buttonPrintSettings = localize(
|
||||
"card.buttons.print_settings",
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProperties.has("monitoredStats")) {
|
||||
this.monitoredStats = undefinedDefault(
|
||||
this.monitoredStats,
|
||||
defaultMonitoredStats,
|
||||
) as PrinterCardStatType[];
|
||||
}
|
||||
|
||||
if (changedProperties.has("selectedPrinterID")) {
|
||||
this.printerEntities = getPrinterEntities(
|
||||
this.hass,
|
||||
this.selectedPrinterID,
|
||||
);
|
||||
|
||||
this.printerEntityIdPart = getPrinterEntityIdPart(this.printerEntities);
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("alwaysShow") ||
|
||||
changedProperties.has("hiddenOverride") ||
|
||||
changedProperties.has("selectedPrinterID")
|
||||
) {
|
||||
this.progressPercent = this._percentComplete();
|
||||
this.hasColorbox =
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"ace_spools",
|
||||
"inactive",
|
||||
).state === "active";
|
||||
this.hasSecondaryColorbox =
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"secondary_multi_color_box_spools",
|
||||
"inactive",
|
||||
).state === "active";
|
||||
if (this.cameraEntityId) {
|
||||
this.cameraEntityState = getEntityState(this.hass, {
|
||||
entity_id: this.cameraEntityId,
|
||||
});
|
||||
}
|
||||
this.lightIsOn = getEntityStateBinary(
|
||||
this.hass,
|
||||
{ entity_id: this.lightEntityId ?? "" },
|
||||
true,
|
||||
false,
|
||||
) as boolean;
|
||||
const printStateString = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_state",
|
||||
"unknown",
|
||||
).state.toLowerCase();
|
||||
this.isPrinting = isPrintStatePrinting(printStateString);
|
||||
this.isHidden = !this.alwaysShow
|
||||
? !this.hiddenOverride && !this.isPrinting
|
||||
: false;
|
||||
this.statusColor = printStateStatusColor(printStateString);
|
||||
this.lightIsOn = getEntityStateBinary(
|
||||
this.hass,
|
||||
{ entity_id: this.lightEntityId ?? "" },
|
||||
true,
|
||||
false,
|
||||
) as boolean;
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const classesCam = {
|
||||
"ac-hidden": !this._showVideo,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="ac-printer-card">
|
||||
<div class="ac-printer-card-mainview">
|
||||
${this._renderHeader()} ${this._renderPrinterContainer()}
|
||||
</div>
|
||||
<anycubic-printercard-camera_view
|
||||
class=${classMap(classesCam)}
|
||||
.showVideo=${this._showVideo}
|
||||
.toggleVideo=${this._toggleVideo}
|
||||
.cameraEntity=${this.cameraEntityState}
|
||||
></anycubic-printercard-camera_view>
|
||||
<anycubic-printercard-multicolorbox_modal_spool
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
.slotColors=${this.slotColors}
|
||||
></anycubic-printercard-multicolorbox_modal_spool>
|
||||
<anycubic-printercard-printsettings_modal
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
></anycubic-printercard-printsettings_modal>
|
||||
<anycubic-printercard-multicolorbox_modal_drying
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
></anycubic-printercard-multicolorbox_modal_drying>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderHeader(): LitTemplateResult {
|
||||
const classesHeader = {
|
||||
"ac-h-justifycenter": !(this.powerEntityId && this.lightEntityId),
|
||||
};
|
||||
|
||||
const stylesDot = {
|
||||
"background-color": this.statusColor,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="ac-printer-card-header ${classMap(classesHeader)}">
|
||||
${this.powerEntityId
|
||||
? html`
|
||||
<button
|
||||
class="ac-printer-card-button-small"
|
||||
.disabled=${this._togglingPower}
|
||||
@click=${this._togglePowerEntity}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<button
|
||||
class="ac-printer-card-button-name"
|
||||
@click=${this._toggleHiddenOveride}
|
||||
>
|
||||
<div
|
||||
class="ac-printer-card-header-status-dot"
|
||||
style=${styleMap(stylesDot)}
|
||||
></div>
|
||||
<p class="ac-printer-card-header-status-text">
|
||||
${this.selectedPrinterDevice?.name}
|
||||
</p>
|
||||
</button>
|
||||
${this.lightEntityId
|
||||
? html`
|
||||
<button
|
||||
class="ac-printer-card-button-small"
|
||||
.disabled=${this._togglingLight}
|
||||
@click=${this._toggleLightEntity}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this.lightIsOn ? mdiLightbulbOn : mdiLightbulbOff}
|
||||
></ha-svg-icon>
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPrinterContainer(): LitTemplateResult {
|
||||
const classesMain = {
|
||||
"ac-card-vertical": !!this.vertical,
|
||||
};
|
||||
const stylesMain = {
|
||||
height: this.isHidden ? "1px" : "auto",
|
||||
opacity: this.isHidden ? 0.0 : 1.0,
|
||||
scale: this.isHidden ? 0.0 : 1.0,
|
||||
};
|
||||
const stylesScaledColLeft = {
|
||||
width: this.vertical
|
||||
? "100%"
|
||||
: this.scaleFactor
|
||||
? String(50 * this.scaleFactor) + "%"
|
||||
: "50%",
|
||||
};
|
||||
const stylesScaledColRight = {
|
||||
width: this.vertical
|
||||
? "100%"
|
||||
: this.scaleFactor
|
||||
? String(50 / this.scaleFactor) + "%"
|
||||
: "50%",
|
||||
};
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="ac-printer-card-infocontainer ${classMap(classesMain)}"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<div
|
||||
class="ac-printer-card-info-animcontainer ${classMap(classesMain)}"
|
||||
style=${styleMap(stylesScaledColLeft)}
|
||||
>
|
||||
<anycubic-printercard-printer_view
|
||||
.hass=${this.hass}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
.scaleFactor=${this.scaleFactor}
|
||||
.toggleVideo=${this._toggleVideo}
|
||||
></anycubic-printercard-printer_view>
|
||||
${this.vertical
|
||||
? html`<p class="ac-printer-card-info-vertprog">
|
||||
${this.round
|
||||
? Math.round(this.progressPercent)
|
||||
: this.progressPercent}%
|
||||
</p>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div
|
||||
class="ac-printer-card-info-statscontainer ${classMap(classesMain)}"
|
||||
style=${styleMap(stylesScaledColRight)}
|
||||
>
|
||||
<anycubic-printercard-stats-component
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.monitoredStats=${this.monitoredStats}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
.progressPercent=${this.progressPercent}
|
||||
.showPercent=${!this.vertical}
|
||||
.round=${this.round}
|
||||
.use_24hr=${this.use_24hr}
|
||||
.temperatureUnit=${this.temperatureUnit}
|
||||
></anycubic-printercard-stats-component>
|
||||
</div>
|
||||
</div>
|
||||
${this._renderPrintSettingsContainer()}
|
||||
${this._renderMultiColorBoxContainer()}
|
||||
${this._renderSecondaryMultiColorBoxContainer()}
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleVideo = (): void => {
|
||||
this._showVideo = !!(this.cameraEntityState && !this._showVideo);
|
||||
};
|
||||
|
||||
private _renderPrintSettingsContainer(): LitTemplateResult {
|
||||
const classesMain = {
|
||||
"ac-card-vertical": !!this.vertical,
|
||||
};
|
||||
const stylesMain = {
|
||||
height: this.isHidden ? "1px" : "auto",
|
||||
opacity: this.isHidden ? 0.0 : 1.0,
|
||||
scale: this.isHidden ? 0.0 : 1.0,
|
||||
};
|
||||
|
||||
return this.showSettingsButton || this.isPrinting
|
||||
? html`
|
||||
<div
|
||||
class="ac-printer-card-infocontainer ${classMap(classesMain)}"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<div
|
||||
class="ac-printer-card-settingssection ${classMap(classesMain)}"
|
||||
>
|
||||
<button
|
||||
class="ac-printer-card-button-settings"
|
||||
@click=${this._openPrintSettingsModal}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiCog}></ha-svg-icon>
|
||||
${this._buttonPrintSettings}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderMultiColorBoxContainer(): LitTemplateResult {
|
||||
const classesMain = {
|
||||
"ac-card-vertical": !!this.vertical,
|
||||
};
|
||||
const stylesMain = {
|
||||
height: this.isHidden ? "1px" : "auto",
|
||||
opacity: this.isHidden ? 0.0 : 1.0,
|
||||
scale: this.isHidden ? 0.0 : 1.0,
|
||||
};
|
||||
|
||||
return this.hasColorbox
|
||||
? html`
|
||||
<div
|
||||
class="ac-printer-card-infocontainer ${classMap(classesMain)}"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<div class="ac-printer-card-mcbsection ${classMap(classesMain)}">
|
||||
<anycubic-printercard-multicolorbox_view
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
.box_id=${0}
|
||||
></anycubic-printercard-multicolorbox_view>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderSecondaryMultiColorBoxContainer(): LitTemplateResult {
|
||||
const classesMain = {
|
||||
"ac-card-vertical": !!this.vertical,
|
||||
};
|
||||
const stylesMain = {
|
||||
height: this.isHidden ? "1px" : "auto",
|
||||
opacity: this.isHidden ? 0.0 : 1.0,
|
||||
scale: this.isHidden ? 0.0 : 1.0,
|
||||
};
|
||||
|
||||
return this.hasSecondaryColorbox
|
||||
? html`
|
||||
<div
|
||||
class="ac-printer-card-infocontainer ${classMap(classesMain)}"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<div class="ac-printer-card-mcbsection ${classMap(classesMain)}">
|
||||
<anycubic-printercard-multicolorbox_view
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
.box_id=${1}
|
||||
></anycubic-printercard-multicolorbox_view>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _openPrintSettingsModal = (): void => {
|
||||
fireEvent(this._printerCardContainer, "ac-printset-modal", {
|
||||
modalOpen: true,
|
||||
});
|
||||
};
|
||||
|
||||
private _toggleLightEntity = (): void => {
|
||||
let targetEntityId: string | undefined = this.lightEntityId;
|
||||
|
||||
if (!targetEntityId && this.printerEntityIdPart) {
|
||||
targetEntityId = getPrinterEntityId(
|
||||
this.printerEntityIdPart,
|
||||
"light",
|
||||
"light",
|
||||
);
|
||||
}
|
||||
|
||||
if ((!targetEntityId || !this.hass?.states?.[targetEntityId]) && this.printerEntities) {
|
||||
for (const entityId in this.printerEntities) {
|
||||
if (entityId.startsWith("light.") && entityId.endsWith("_light")) {
|
||||
targetEntityId = entityId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetEntityId && this.hass?.states?.[targetEntityId]) {
|
||||
this._togglingLight = true;
|
||||
this.hass
|
||||
.callService("homeassistant", "toggle", {
|
||||
entity_id: targetEntityId,
|
||||
})
|
||||
.then(() => {
|
||||
this._togglingLight = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._togglingLight = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private _togglePowerEntity = (): void => {
|
||||
if (this.powerEntityId) {
|
||||
this._togglingPower = true;
|
||||
this.hass
|
||||
.callService("homeassistant", "toggle", {
|
||||
entity_id: this.powerEntityId,
|
||||
})
|
||||
.then(() => {
|
||||
this._togglingPower = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._togglingPower = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private _toggleHiddenOveride = (): void => {
|
||||
this.hiddenOverride = !this.hiddenOverride;
|
||||
};
|
||||
|
||||
private _percentComplete(): number {
|
||||
return Number(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_progress",
|
||||
-1.0,
|
||||
).state,
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ac-printer-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
box-sizing: border-box;
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
margin: 0px;
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
}
|
||||
|
||||
.ac-printer-card-mainview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-printer-card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ac-h-justifycenter {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ac-printer-card-button-small {
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 22px;
|
||||
line-height: 22px;
|
||||
box-sizing: border-box;
|
||||
padding: 0px;
|
||||
margin-right: 24px;
|
||||
margin-left: 24px;
|
||||
cursor: pointer;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.ac-printer-card-button-settings {
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
font-size: 18px;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 12px;
|
||||
margin-right: 24px;
|
||||
margin-left: 24px;
|
||||
cursor: pointer;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.ac-printer-card-button-settings:hover {
|
||||
background-color: #7f7f7f36;
|
||||
}
|
||||
|
||||
.ac-printer-card-button-settings:active {
|
||||
background-color: #7f7f7f5e;
|
||||
}
|
||||
|
||||
.ac-printer-card-button-name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
padding: 24px;
|
||||
}
|
||||
.ac-printer-card-header-status-dot {
|
||||
margin: 0px 10px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ac-printer-card-header-status-text {
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
margin: 0px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.ac-printer-card-infocontainer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ac-printer-card-infocontainer.ac-card-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ac-printer-card-info-animcontainer {
|
||||
box-sizing: border-box;
|
||||
padding: 0px 8px 32px 8px;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ac-printer-card-info-animcontainer.ac-card-vertical {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding-left: 64px;
|
||||
padding-right: 64px;
|
||||
}
|
||||
|
||||
anycubic-printercard-printer_view {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ac-printer-card-info-vertprog {
|
||||
width: 50%;
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
anycubic-printercard-printer_view.ac-card-vertical {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.ac-printer-card-info-statscontainer {
|
||||
box-sizing: border-box;
|
||||
padding: 0px 16px 32px 8px;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ac-printer-card-info-statscontainer.ac-card-vertical {
|
||||
padding-left: 32px;
|
||||
padding-right: 32px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ac-printer-card-mcbsection {
|
||||
box-sizing: border-box;
|
||||
padding: 6px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ac-printer-card-mcbsection.ac-card-vertical {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ac-hidden {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import {
|
||||
CAMERA_ENTITY_DOMAINS,
|
||||
LIGHT_ENTITY_DOMAINS,
|
||||
SWITCH_ENTITY_DOMAINS,
|
||||
} from "../../../const";
|
||||
|
||||
import { HASSDomEvent, fireEvent } from "../../../fire_event";
|
||||
|
||||
import {
|
||||
getDefaultCardConfig,
|
||||
getPrinterEntities,
|
||||
getPrinterEntityIdPart,
|
||||
getPrinterSensorStateObj,
|
||||
isLCDPrinter,
|
||||
} from "../../../helpers";
|
||||
|
||||
import {
|
||||
AnycubicCardConfig,
|
||||
CalculatedTimeType,
|
||||
FormChangeDetail,
|
||||
HaFormBaseSchema,
|
||||
HassDeviceList,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
PageChangeDetail,
|
||||
PrinterCardStatType,
|
||||
StatTypeACE,
|
||||
StatTypeFDM,
|
||||
StatTypeGeneral,
|
||||
StatTypeLCD,
|
||||
TemperatureUnit,
|
||||
} from "../../../types";
|
||||
|
||||
import "../../ui/multi-select-reorder.ts";
|
||||
|
||||
const defaultConfig = getDefaultCardConfig();
|
||||
|
||||
@customElement("anycubic-printercard-configure")
|
||||
export class AnycubicPrintercardConfigure extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "card-config" })
|
||||
public cardConfig!: AnycubicCardConfig;
|
||||
|
||||
@property()
|
||||
public printers!: HassDeviceList;
|
||||
|
||||
@state()
|
||||
private configPage: string = "main";
|
||||
|
||||
@state()
|
||||
private availableStats: object = {};
|
||||
|
||||
@state()
|
||||
private formSchemaMain: HaFormBaseSchema[] = [];
|
||||
|
||||
@state()
|
||||
private formSchemaColours: HaFormBaseSchema[] = [];
|
||||
|
||||
@state()
|
||||
private printerEntities: HassEntityInfos;
|
||||
|
||||
@state()
|
||||
private printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
private hasColorbox: boolean = false;
|
||||
|
||||
@state()
|
||||
private isLCD: boolean = false;
|
||||
|
||||
@state()
|
||||
private _tabMain: string;
|
||||
|
||||
@state()
|
||||
private _tabStats: string;
|
||||
|
||||
@state()
|
||||
private _tabColours: string;
|
||||
|
||||
@state()
|
||||
private _labelPrinter_id: string;
|
||||
|
||||
@state()
|
||||
private _labelVertical: string;
|
||||
|
||||
@state()
|
||||
private _labelRound: string;
|
||||
|
||||
@state()
|
||||
private _labelUse_24hr: string;
|
||||
|
||||
@state()
|
||||
private _labelShowSettingsButton: string;
|
||||
|
||||
@state()
|
||||
private _labelAlwaysShow: string;
|
||||
|
||||
@state()
|
||||
private _labelTemperatureUnit: string;
|
||||
|
||||
@state()
|
||||
private _labelLightEntityId: string;
|
||||
|
||||
@state()
|
||||
private _labelPowerEntityId: string;
|
||||
|
||||
@state()
|
||||
private _labelCameraEntityId: string;
|
||||
|
||||
@state()
|
||||
private _labelScaleFactor: string;
|
||||
|
||||
@state()
|
||||
private _labelSlotColors: string;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._tabMain = localize("card.configure.tabs.main", this.language);
|
||||
this._tabStats = localize("card.configure.tabs.stats", this.language);
|
||||
this._tabColours = localize("card.configure.tabs.colours", this.language);
|
||||
this._labelPrinter_id = localize(
|
||||
"card.configure.labels.printer_id",
|
||||
this.language,
|
||||
);
|
||||
this._labelVertical = localize(
|
||||
"card.configure.labels.vertical",
|
||||
this.language,
|
||||
);
|
||||
this._labelRound = localize("card.configure.labels.round", this.language);
|
||||
this._labelUse_24hr = localize(
|
||||
"card.configure.labels.use_24hr",
|
||||
this.language,
|
||||
);
|
||||
this._labelShowSettingsButton = localize(
|
||||
"card.configure.labels.show_settings_button",
|
||||
this.language,
|
||||
);
|
||||
this._labelAlwaysShow = localize(
|
||||
"card.configure.labels.always_show",
|
||||
this.language,
|
||||
);
|
||||
this._labelTemperatureUnit = localize(
|
||||
"card.configure.labels.temperature_unit",
|
||||
this.language,
|
||||
);
|
||||
this._labelLightEntityId = localize(
|
||||
"card.configure.labels.light_entity_id",
|
||||
this.language,
|
||||
);
|
||||
this._labelPowerEntityId = localize(
|
||||
"card.configure.labels.power_entity_id",
|
||||
this.language,
|
||||
);
|
||||
this._labelCameraEntityId = localize(
|
||||
"card.configure.labels.camera_entity_id",
|
||||
this.language,
|
||||
);
|
||||
this._labelScaleFactor = localize(
|
||||
"card.configure.labels.scale_factor",
|
||||
this.language,
|
||||
);
|
||||
this._labelSlotColors = localize(
|
||||
"card.configure.labels.slot_colors",
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProperties.has("hass") || changedProperties.has("cardConfig")) {
|
||||
this.printerEntities = getPrinterEntities(
|
||||
this.hass,
|
||||
this.cardConfig.printer_id,
|
||||
);
|
||||
|
||||
this.printerEntityIdPart = getPrinterEntityIdPart(this.printerEntities);
|
||||
|
||||
this.isLCD = isLCDPrinter(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
);
|
||||
this.hasColorbox =
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"ace_spools",
|
||||
"inactive",
|
||||
).state === "active";
|
||||
this.availableStats = {
|
||||
...StatTypeGeneral,
|
||||
...CalculatedTimeType,
|
||||
};
|
||||
if (this.isLCD) {
|
||||
this.availableStats = {
|
||||
...this.availableStats,
|
||||
...StatTypeLCD,
|
||||
};
|
||||
} else {
|
||||
this.availableStats = {
|
||||
...this.availableStats,
|
||||
...StatTypeFDM,
|
||||
};
|
||||
}
|
||||
if (this.hasColorbox) {
|
||||
this.availableStats = {
|
||||
...this.availableStats,
|
||||
...StatTypeACE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("printers") ||
|
||||
changedProperties.has("language")
|
||||
) {
|
||||
this.formSchemaMain = this._computeSchemaMain();
|
||||
this.formSchemaColours = this._computeSchemaColours();
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="ac-printer-card-configure-cont">
|
||||
${this._renderMenu()} ${this._renderConfMain()}
|
||||
${this._renderConfColours()} ${this._renderConfStats()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderConfMain(): LitTemplateResult {
|
||||
return this.configPage === "main"
|
||||
? html`
|
||||
<div class="ac-printer-card-configure-conf">
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.cardConfig}
|
||||
.schema=${this.formSchemaMain}
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._formValueChanged}
|
||||
></ha-form>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderConfStats(): LitTemplateResult {
|
||||
return this.configPage === "stats"
|
||||
? html`
|
||||
<div class="ac-printer-card-configure-conf">
|
||||
<p class="ac-cconf-label">Choose Monitored Stats</p>
|
||||
<anycubic-ui-multi-select-reorder
|
||||
.availableOptions=${this.availableStats}
|
||||
.initialItems=${this.cardConfig.monitoredStats}
|
||||
.onChange=${this._selectedStatsChanged}
|
||||
></anycubic-ui-multi-select-reorder>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderConfColours(): LitTemplateResult {
|
||||
return this.configPage === "colours"
|
||||
? html`
|
||||
<div class="ac-printer-card-configure-conf">
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.cardConfig}
|
||||
.schema=${this.formSchemaColours}
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._formValueChanged}
|
||||
></ha-form>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderMenu(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="header">
|
||||
<ha-tabs
|
||||
scrollable
|
||||
attr-for-selected="page-name"
|
||||
.selected=${this.configPage}
|
||||
@iron-activate=${this._handlePageSelected}
|
||||
>
|
||||
<paper-tab page-name="main">${this._tabMain}</paper-tab>
|
||||
<paper-tab page-name="stats">${this._tabStats}</paper-tab>
|
||||
${this.hasColorbox
|
||||
? html`<paper-tab page-name="colours">
|
||||
${this._tabColours}
|
||||
</paper-tab>`
|
||||
: nothing}
|
||||
</ha-tabs>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handlePageSelected = (ev: HASSDomEvent<PageChangeDetail>): void => {
|
||||
const newPage = ev.detail.item.getAttribute("page-name") as string;
|
||||
if (newPage !== this.configPage) {
|
||||
this.configPage = newPage;
|
||||
}
|
||||
};
|
||||
|
||||
private _selectedStatsChanged = (selected: PrinterCardStatType[]): void => {
|
||||
this.cardConfig.monitoredStats = selected;
|
||||
this._configChanged(this.cardConfig);
|
||||
};
|
||||
|
||||
private _configChanged(newConfig: AnycubicCardConfig): void {
|
||||
const filteredConfig = Object.keys(newConfig)
|
||||
.filter((key) => newConfig[key] !== defaultConfig[key])
|
||||
.reduce((fConf: AnycubicCardConfig, key: string) => {
|
||||
fConf[key] = newConfig[key as keyof AnycubicCardConfig];
|
||||
return fConf;
|
||||
}, {});
|
||||
fireEvent(this, "config-changed", { config: filteredConfig });
|
||||
}
|
||||
|
||||
private _formValueChanged = (ev: HASSDomEvent<FormChangeDetail>): void => {
|
||||
this.cardConfig = ev.detail.value;
|
||||
this._configChanged(this.cardConfig);
|
||||
};
|
||||
|
||||
private _computeLabel = (schema: HaFormBaseSchema): string => {
|
||||
switch (schema.name) {
|
||||
case "printer_id":
|
||||
return this._labelPrinter_id;
|
||||
case "vertical":
|
||||
return this._labelVertical;
|
||||
case "round":
|
||||
return this._labelRound;
|
||||
case "use_24hr":
|
||||
return this._labelUse_24hr;
|
||||
case "showSettingsButton":
|
||||
return this._labelShowSettingsButton;
|
||||
case "alwaysShow":
|
||||
return this._labelAlwaysShow;
|
||||
case "temperatureUnit":
|
||||
return this._labelTemperatureUnit;
|
||||
case "lightEntityId":
|
||||
return this._labelLightEntityId;
|
||||
case "powerEntityId":
|
||||
return this._labelPowerEntityId;
|
||||
case "cameraEntityId":
|
||||
return this._labelCameraEntityId;
|
||||
case "scaleFactor":
|
||||
return this._labelScaleFactor;
|
||||
case "slotColors":
|
||||
return this._labelSlotColors;
|
||||
default:
|
||||
return this._labelPrinter_id;
|
||||
}
|
||||
};
|
||||
|
||||
private _computeSchemaMain(): HaFormBaseSchema[] {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!this.printers) {
|
||||
return [];
|
||||
}
|
||||
const printerOptions = Object.keys(this.printers).map(
|
||||
(printerID, _index) => ({
|
||||
value: printerID,
|
||||
label: this.printers[printerID].name,
|
||||
}),
|
||||
);
|
||||
return [
|
||||
{
|
||||
name: "printer_id",
|
||||
selector: {
|
||||
select: {
|
||||
options: printerOptions,
|
||||
mode: "dropdown",
|
||||
multiple: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vertical",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "round",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "use_24hr",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "temperatureUnit",
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{
|
||||
value: TemperatureUnit.C,
|
||||
label: `°${TemperatureUnit.C}`,
|
||||
},
|
||||
{
|
||||
value: TemperatureUnit.F,
|
||||
label: `°${TemperatureUnit.F}`,
|
||||
},
|
||||
],
|
||||
mode: "list",
|
||||
multiple: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "alwaysShow",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "showSettingsButton",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "scaleFactor",
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{
|
||||
value: 1,
|
||||
label: "1",
|
||||
},
|
||||
{
|
||||
value: 0.75,
|
||||
label: "0.75",
|
||||
},
|
||||
{
|
||||
value: 0.5,
|
||||
label: "0.5",
|
||||
},
|
||||
],
|
||||
mode: "list",
|
||||
multiple: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lightEntityId",
|
||||
selector: { entity: { domain: LIGHT_ENTITY_DOMAINS } },
|
||||
},
|
||||
{
|
||||
name: "powerEntityId",
|
||||
selector: { entity: { domain: SWITCH_ENTITY_DOMAINS } },
|
||||
},
|
||||
{
|
||||
name: "cameraEntityId",
|
||||
selector: { entity: { domain: CAMERA_ENTITY_DOMAINS } },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private _computeSchemaColours(): HaFormBaseSchema[] {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
return this.printers
|
||||
? [
|
||||
{
|
||||
name: "slotColors",
|
||||
selector: {
|
||||
text: {
|
||||
multiple: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-tabs {
|
||||
margin-left: max(env(safe-area-inset-left), 24px);
|
||||
margin-right: max(env(safe-area-inset-right), 24px);
|
||||
--paper-tabs-selection-bar-color: var(--primary-color);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ac-printer-card-configure-conf {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ac-cconf-label {
|
||||
margin-bottom: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { animate, Options as motionOptions } from "@lit-labs/motion";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import { HASSDomEvent } from "../../../fire_event";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import {
|
||||
getPrinterDryingButtonStateObj,
|
||||
getPrinterEntityId,
|
||||
isPrinterButtonStateAvailable,
|
||||
} from "../../../helpers";
|
||||
|
||||
import {
|
||||
AnycubicDryingPresetEntity,
|
||||
HassDevice,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
ModalEventDrying,
|
||||
} from "../../../types";
|
||||
|
||||
import { commonModalStyle } from "../../ui/modal-styles";
|
||||
|
||||
import "../../ui/select-dropdown.ts";
|
||||
|
||||
const animOptionsCard: motionOptions = {
|
||||
keyframeOptions: {
|
||||
duration: 250,
|
||||
direction: "alternate",
|
||||
easing: "ease-in-out",
|
||||
},
|
||||
properties: ["height", "opacity", "scale"],
|
||||
};
|
||||
|
||||
const PRIMARY_DRYING_PRESET_1 = "drying_preset_1";
|
||||
const PRIMARY_DRYING_PRESET_2 = "drying_preset_2";
|
||||
const PRIMARY_DRYING_PRESET_3 = "drying_preset_3";
|
||||
const PRIMARY_DRYING_PRESET_4 = "drying_preset_4";
|
||||
const PRIMARY_DRYING_STOP = "drying_stop";
|
||||
|
||||
const SECONDARY_PREFIX = "secondary_";
|
||||
|
||||
const SECONDARY_DRYING_PRESET_1 = SECONDARY_PREFIX + PRIMARY_DRYING_PRESET_1;
|
||||
const SECONDARY_DRYING_PRESET_2 = SECONDARY_PREFIX + PRIMARY_DRYING_PRESET_2;
|
||||
const SECONDARY_DRYING_PRESET_3 = SECONDARY_PREFIX + PRIMARY_DRYING_PRESET_3;
|
||||
const SECONDARY_DRYING_PRESET_4 = SECONDARY_PREFIX + PRIMARY_DRYING_PRESET_4;
|
||||
const SECONDARY_DRYING_STOP = SECONDARY_PREFIX + PRIMARY_DRYING_STOP;
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-multicolorbox_modal_drying")
|
||||
export class AnycubicPrintercardMulticolorboxModalDrying extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@property({ attribute: "printer-entities" })
|
||||
public printerEntities: HassEntityInfos;
|
||||
|
||||
@property({ attribute: "printer-entity-id-part" })
|
||||
public printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
private box_id: number = 0;
|
||||
|
||||
@state()
|
||||
private _dryingPresetId1: string = PRIMARY_DRYING_PRESET_1;
|
||||
|
||||
@state()
|
||||
private _dryingPresetId2: string = PRIMARY_DRYING_PRESET_2;
|
||||
|
||||
@state()
|
||||
private _dryingPresetId3: string = PRIMARY_DRYING_PRESET_3;
|
||||
|
||||
@state()
|
||||
private _dryingPresetId4: string = PRIMARY_DRYING_PRESET_4;
|
||||
|
||||
@state()
|
||||
private _dryingStopId: string = PRIMARY_DRYING_STOP;
|
||||
|
||||
@state()
|
||||
private _hasDryingPreset1: boolean = false;
|
||||
|
||||
@state()
|
||||
private _hasDryingPreset2: boolean = false;
|
||||
|
||||
@state()
|
||||
private _hasDryingPreset3: boolean = false;
|
||||
|
||||
@state()
|
||||
private _hasDryingPreset4: boolean = false;
|
||||
|
||||
@state()
|
||||
private _hasDryingStop: boolean = false;
|
||||
|
||||
@state()
|
||||
private _dryingPresetTemp1: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetDur1: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetTemp2: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetDur2: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetTemp3: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetDur3: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetTemp4: string = "";
|
||||
|
||||
@state()
|
||||
private _dryingPresetDur4: string = "";
|
||||
|
||||
@state()
|
||||
private _isOpen: boolean = false;
|
||||
|
||||
@state()
|
||||
private _heading: string;
|
||||
|
||||
@state()
|
||||
private _buttonTextPreset: string;
|
||||
|
||||
@state()
|
||||
private _buttonTextMinutes: string;
|
||||
|
||||
@state()
|
||||
private _buttonStopDrying: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.addEventListener("click", (e) => {
|
||||
this._closeModal(e);
|
||||
});
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.parentElement?.addEventListener(
|
||||
"ac-mcbdry-modal",
|
||||
this._handleModalEvent,
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
this.parentElement?.removeEventListener(
|
||||
"ac-mcbdry-modal",
|
||||
this._handleModalEvent,
|
||||
);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._heading = localize("card.drying_settings.heading", this.language);
|
||||
this._buttonTextPreset = localize(
|
||||
"card.drying_settings.button_preset",
|
||||
this.language,
|
||||
);
|
||||
this._buttonTextMinutes = localize(
|
||||
"card.drying_settings.button_minutes",
|
||||
this.language,
|
||||
);
|
||||
this._buttonStopDrying = localize(
|
||||
"card.drying_settings.button_stop_drying",
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
|
||||
if (changedProperties.has("box_id")) {
|
||||
if (this.box_id === 1) {
|
||||
this._dryingPresetId1 = SECONDARY_DRYING_PRESET_1;
|
||||
this._dryingPresetId2 = SECONDARY_DRYING_PRESET_2;
|
||||
this._dryingPresetId3 = SECONDARY_DRYING_PRESET_3;
|
||||
this._dryingPresetId4 = SECONDARY_DRYING_PRESET_4;
|
||||
this._dryingStopId = SECONDARY_DRYING_STOP;
|
||||
} else {
|
||||
this._dryingPresetId1 = PRIMARY_DRYING_PRESET_1;
|
||||
this._dryingPresetId2 = PRIMARY_DRYING_PRESET_2;
|
||||
this._dryingPresetId3 = PRIMARY_DRYING_PRESET_3;
|
||||
this._dryingPresetId4 = PRIMARY_DRYING_PRESET_4;
|
||||
this._dryingStopId = PRIMARY_DRYING_STOP;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("selectedPrinterDevice")
|
||||
) {
|
||||
const dryingPresetState1: AnycubicDryingPresetEntity =
|
||||
getPrinterDryingButtonStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._dryingPresetId1,
|
||||
) as AnycubicDryingPresetEntity;
|
||||
this._hasDryingPreset1 =
|
||||
isPrinterButtonStateAvailable(dryingPresetState1);
|
||||
this._dryingPresetTemp1 = String(
|
||||
dryingPresetState1.attributes.temperature,
|
||||
);
|
||||
this._dryingPresetDur1 = String(dryingPresetState1.attributes.duration);
|
||||
const dryingPresetState2: AnycubicDryingPresetEntity =
|
||||
getPrinterDryingButtonStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._dryingPresetId2,
|
||||
) as AnycubicDryingPresetEntity;
|
||||
this._hasDryingPreset2 =
|
||||
isPrinterButtonStateAvailable(dryingPresetState2);
|
||||
this._dryingPresetTemp2 = String(
|
||||
dryingPresetState2.attributes.temperature,
|
||||
);
|
||||
this._dryingPresetDur2 = String(dryingPresetState2.attributes.duration);
|
||||
const dryingPresetState3: AnycubicDryingPresetEntity =
|
||||
getPrinterDryingButtonStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._dryingPresetId3,
|
||||
) as AnycubicDryingPresetEntity;
|
||||
this._hasDryingPreset3 =
|
||||
isPrinterButtonStateAvailable(dryingPresetState3);
|
||||
this._dryingPresetTemp3 = String(
|
||||
dryingPresetState3.attributes.temperature,
|
||||
);
|
||||
this._dryingPresetDur3 = String(dryingPresetState3.attributes.duration);
|
||||
const dryingPresetState4: AnycubicDryingPresetEntity =
|
||||
getPrinterDryingButtonStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._dryingPresetId4,
|
||||
) as AnycubicDryingPresetEntity;
|
||||
this._hasDryingPreset4 =
|
||||
isPrinterButtonStateAvailable(dryingPresetState4);
|
||||
this._dryingPresetTemp4 = String(
|
||||
dryingPresetState4.attributes.temperature,
|
||||
);
|
||||
this._dryingPresetDur4 = String(dryingPresetState4.attributes.duration);
|
||||
const dryingStopState = getPrinterDryingButtonStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._dryingStopId,
|
||||
);
|
||||
this._hasDryingStop = isPrinterButtonStateAvailable(dryingStopState);
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues<this>): void {
|
||||
super.update(changedProperties);
|
||||
if (this._isOpen) {
|
||||
this.style.display = "block";
|
||||
} else {
|
||||
this.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesMain = {
|
||||
height: "auto",
|
||||
opacity: 1.0,
|
||||
scale: 1.0,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="ac-modal-container"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<span class="ac-modal-close" @click=${this._closeModal}>×</span>
|
||||
<div class="ac-modal-card" @click=${this._cardClick}>
|
||||
${this._renderCard()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_renderCard(): LitTemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
<div class="ac-drying-header">${this._heading}</div>
|
||||
<div class="ac-drying-buttonscont">
|
||||
${this._hasDryingPreset1
|
||||
? html`
|
||||
<div class="ac-drying-buttoncont">
|
||||
<ha-control-button @click=${this._handleDryingPreset1}>
|
||||
${this._buttonTextPreset} 1<br />
|
||||
${this._dryingPresetDur1} ${this._buttonTextMinutes} @
|
||||
${this._dryingPresetTemp1}°C
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this._hasDryingPreset2
|
||||
? html`
|
||||
<div class="ac-drying-buttoncont">
|
||||
<ha-control-button @click=${this._handleDryingPreset2}>
|
||||
${this._buttonTextPreset} 2<br />
|
||||
${this._dryingPresetDur2} ${this._buttonTextMinutes} @
|
||||
${this._dryingPresetTemp2}°C
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this._hasDryingPreset3
|
||||
? html`
|
||||
<div class="ac-drying-buttoncont">
|
||||
<ha-control-button @click=${this._handleDryingPreset3}>
|
||||
${this._buttonTextPreset} 3<br />
|
||||
${this._dryingPresetDur3} ${this._buttonTextMinutes} @
|
||||
${this._dryingPresetTemp3}°C
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this._hasDryingPreset4
|
||||
? html`
|
||||
<div class="ac-drying-buttoncont">
|
||||
<ha-control-button @click=${this._handleDryingPreset4}>
|
||||
${this._buttonTextPreset} 4<br />
|
||||
${this._dryingPresetDur4} ${this._buttonTextMinutes} @
|
||||
${this._dryingPresetTemp4}°C
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${this._hasDryingStop
|
||||
? html`
|
||||
<div class="ac-flex-break"></div>
|
||||
<div class="ac-drying-buttoncont">
|
||||
<ha-control-button @click=${this._handleDryingStop}>
|
||||
${this._buttonStopDrying}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _pressHassButton(suffix: string): void {
|
||||
if (this.printerEntityIdPart) {
|
||||
this.hass
|
||||
.callService("button", "press", {
|
||||
entity_id: getPrinterEntityId(
|
||||
this.printerEntityIdPart,
|
||||
"button",
|
||||
suffix,
|
||||
),
|
||||
})
|
||||
.then()
|
||||
.catch((_e: unknown) => {
|
||||
// Show in error modal
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDryingPreset1 = (): void => {
|
||||
this._pressHassButton(this._dryingPresetId1);
|
||||
this._closeModal();
|
||||
};
|
||||
|
||||
private _handleDryingPreset2 = (): void => {
|
||||
this._pressHassButton(this._dryingPresetId2);
|
||||
this._closeModal();
|
||||
};
|
||||
|
||||
private _handleDryingPreset3 = (): void => {
|
||||
this._pressHassButton(this._dryingPresetId3);
|
||||
this._closeModal();
|
||||
};
|
||||
|
||||
private _handleDryingPreset4 = (): void => {
|
||||
this._pressHassButton(this._dryingPresetId4);
|
||||
this._closeModal();
|
||||
};
|
||||
|
||||
private _handleDryingStop = (): void => {
|
||||
this._pressHassButton(this._dryingStopId);
|
||||
this._closeModal();
|
||||
};
|
||||
|
||||
private _handleModalEvent = (evt: Event): void => {
|
||||
const e = evt as HASSDomEvent<ModalEventDrying>;
|
||||
e.stopPropagation();
|
||||
if (e.detail.modalOpen) {
|
||||
this._isOpen = true;
|
||||
this.box_id = Number(e.detail.box_id);
|
||||
}
|
||||
};
|
||||
|
||||
private _closeModal = (e?: Event | undefined): void => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
this._isOpen = false;
|
||||
this.box_id = 0;
|
||||
};
|
||||
|
||||
private _cardClick = (e: Event): void => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
${commonModalStyle}
|
||||
|
||||
.ac-drying-header {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
ha-control-button {
|
||||
min-width: 150px;
|
||||
font-size: 14px;
|
||||
min-height: 55px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ac-flex-break {
|
||||
flex-basis: 100%;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.ac-drying-buttonscont {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ac-drying-buttoncont {
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { query } from "lit/decorators/query.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { animate, Options as motionOptions } from "@lit-labs/motion";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import "../../../lib/colorpicker/ColorPicker.js";
|
||||
|
||||
import { platform } from "../../../const";
|
||||
import { HASSDomEvent } from "../../../fire_event";
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import { materialTypeFromString } from "../../../helpers";
|
||||
import {
|
||||
AnycubicMaterialType,
|
||||
AnycubicSpoolInfo,
|
||||
ColorPicker,
|
||||
ColourPickEvent,
|
||||
DomClickEvent,
|
||||
DropdownEvent,
|
||||
EvtTargColourPreset,
|
||||
HassDevice,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
ModalEventSpool,
|
||||
} from "../../../types";
|
||||
|
||||
import { commonModalStyle } from "../../ui/modal-styles";
|
||||
|
||||
import "../../ui/select-dropdown.ts";
|
||||
|
||||
const animOptionsCard: motionOptions = {
|
||||
keyframeOptions: {
|
||||
duration: 250,
|
||||
direction: "alternate",
|
||||
easing: "ease-in-out",
|
||||
},
|
||||
properties: ["height", "opacity", "scale"],
|
||||
};
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-multicolorbox_modal_spool")
|
||||
export class AnycubicPrintercardMulticolorboxModalSpool extends LitElement {
|
||||
@query("color-picker")
|
||||
private _elColorPicker: ColorPicker | undefined;
|
||||
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@property({ attribute: "slot-colors" })
|
||||
public slotColors?: string[];
|
||||
|
||||
@state()
|
||||
private box_id: number = 0;
|
||||
|
||||
@state()
|
||||
private spoolList: AnycubicSpoolInfo[] = [];
|
||||
|
||||
@state()
|
||||
private spool_index: number = -1;
|
||||
|
||||
@state()
|
||||
private material_type: AnycubicMaterialType | undefined;
|
||||
|
||||
@state()
|
||||
private color: number[] | string | undefined;
|
||||
|
||||
@state()
|
||||
private _isOpen: boolean = false;
|
||||
|
||||
@state()
|
||||
private _heading: string;
|
||||
|
||||
@state()
|
||||
private _labelSelectMaterial: string;
|
||||
|
||||
@state()
|
||||
private _labelSelectColour: string;
|
||||
|
||||
@state()
|
||||
private _buttonSave: string;
|
||||
|
||||
@state()
|
||||
private _changingSlot: boolean = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.addEventListener("click", (e) => {
|
||||
this._closeModal(e);
|
||||
});
|
||||
this.addEventListener("ac-select-dropdown", this._handleDropdownEvent);
|
||||
this.addEventListener("colorchanged", this._handleColourEvent);
|
||||
this.addEventListener("colorpicked", this._handleColourPickEvent);
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.parentElement?.addEventListener(
|
||||
"ac-mcb-modal",
|
||||
this._handleModalEvent,
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
this.parentElement?.removeEventListener(
|
||||
"ac-mcb-modal",
|
||||
this._handleModalEvent,
|
||||
);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("language")) {
|
||||
this._heading = localize("card.spool_settings.heading", this.language);
|
||||
this._labelSelectMaterial = localize(
|
||||
"card.spool_settings.label_select_material",
|
||||
this.language,
|
||||
);
|
||||
this._labelSelectColour = localize(
|
||||
"card.spool_settings.label_select_colour",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSave = localize("common.actions.save", this.language);
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues<this>): void {
|
||||
super.update(changedProperties);
|
||||
if (this._isOpen) {
|
||||
this.style.display = "block";
|
||||
} else {
|
||||
this.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesMain = {
|
||||
height: "auto",
|
||||
opacity: 1.0,
|
||||
scale: 1.0,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="ac-modal-container"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<span class="ac-modal-close" @click=${this._closeModal}>×</span>
|
||||
<div class="ac-modal-card" @click=${this._cardClick}>
|
||||
${this.color ? this._renderCard() : nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_renderCard(): LitTemplateResult {
|
||||
return this.spool_index >= 0
|
||||
? html`
|
||||
<div>
|
||||
<div class="ac-slot-title">
|
||||
${this._heading}: ${this.spool_index + 1}
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p class="ac-modal-label">${this._labelSelectMaterial}:</p>
|
||||
<anycubic-ui-select-dropdown
|
||||
.availableOptions=${AnycubicMaterialType}
|
||||
.placeholder=${AnycubicMaterialType.PLA}
|
||||
.initialItem=${this.material_type}
|
||||
></anycubic-ui-select-dropdown>
|
||||
</div>
|
||||
${this._renderPresets()}
|
||||
<div>
|
||||
<p class="ac-modal-label">${this._labelSelectColour}:</p>
|
||||
<color-picker .value=${this.color}></color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-save-settings">
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSlot}
|
||||
@click=${this._handleSaveButton}
|
||||
>
|
||||
${this._buttonSave}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderPresets(): LitTemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
<p class="ac-modal-label">Choose Preset Colour:</p>
|
||||
<div class="ac-mcb-presets">
|
||||
${this.slotColors
|
||||
? map(this.slotColors, (preset, _index) => {
|
||||
const presetStyle = {
|
||||
"background-color": preset,
|
||||
};
|
||||
return html`
|
||||
<div
|
||||
class="ac-mcb-preset-color"
|
||||
style=${styleMap(presetStyle)}
|
||||
.preset=${preset}
|
||||
@click=${this._colourPresetChange}
|
||||
>
|
||||
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _colourPresetChange = (
|
||||
ev: DomClickEvent<EvtTargColourPreset>,
|
||||
): void => {
|
||||
this.color = ev.currentTarget.preset;
|
||||
if (this._elColorPicker) {
|
||||
this._elColorPicker.color = this.color;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleModalEvent = (evt: Event): void => {
|
||||
const e = evt as HASSDomEvent<ModalEventSpool>;
|
||||
e.stopPropagation();
|
||||
if (e.detail.modalOpen) {
|
||||
this._isOpen = true;
|
||||
this.box_id = Number(e.detail.box_id);
|
||||
this.spool_index = Number(e.detail.spool_index);
|
||||
this.material_type = materialTypeFromString(e.detail.material_type);
|
||||
this.color = e.detail.color;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleDropdownEvent = (evt: Event): void => {
|
||||
const e = evt as HASSDomEvent<DropdownEvent<string, string>>;
|
||||
e.stopPropagation();
|
||||
if (e.detail.value) {
|
||||
this.material_type = materialTypeFromString(e.detail.value);
|
||||
}
|
||||
};
|
||||
|
||||
private _handleColourEvent = (evt: Event): void => {
|
||||
const e = evt as HASSDomEvent<ColourPickEvent>;
|
||||
e.stopPropagation();
|
||||
if (e.detail.color) {
|
||||
this.color = e.detail.color.rgb;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleColourPickEvent = (e: Event): void => {
|
||||
this._handleColourEvent(e);
|
||||
if (!this._changingSlot) {
|
||||
this._submitSlotChanges();
|
||||
}
|
||||
};
|
||||
|
||||
private _handleSaveButton = (): void => {
|
||||
this._submitSlotChanges();
|
||||
};
|
||||
|
||||
private _serviceAvailable(serviceName: string): boolean {
|
||||
return Boolean(this.hass?.services?.[platform]?.[serviceName]);
|
||||
}
|
||||
|
||||
private _submitSlotChanges(): void {
|
||||
if (
|
||||
this.selectedPrinterDevice &&
|
||||
this.material_type &&
|
||||
this.spool_index >= 0 &&
|
||||
this.color &&
|
||||
this.color.length >= 3
|
||||
) {
|
||||
const serv = `multi_color_box_set_slot_${this.material_type.toLowerCase()}`;
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
this._closeModal();
|
||||
return;
|
||||
}
|
||||
this._changingSlot = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
box_id: this.box_id,
|
||||
slot_number: this.spool_index + 1,
|
||||
slot_color_red: this.color[0],
|
||||
slot_color_green: this.color[1],
|
||||
slot_color_blue: this.color[2],
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSlot = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSlot = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
private _closeModal = (e?: Event | undefined): void => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
this._isOpen = false;
|
||||
this.spool_index = -1;
|
||||
this.material_type = undefined;
|
||||
this.color = undefined;
|
||||
this.box_id = 0;
|
||||
};
|
||||
|
||||
private _cardClick = (e: Event): void => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
${commonModalStyle}
|
||||
|
||||
.ac-slot-title {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ac-mcb-presets {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ac-mcb-preset-color {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
margin: 20px 10px;
|
||||
}
|
||||
|
||||
ha-control-button {
|
||||
min-width: 150px;
|
||||
margin: 30px auto 0px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
color-picker {
|
||||
--font-fam: var(--token-font-family-primary);
|
||||
--bg-color: var(--ha-card-background);
|
||||
--label-color: var(--secondary-text-color);
|
||||
--form-border-color: var(--ha-card-background);
|
||||
--input-active-border-color: var(--primary-color);
|
||||
--input-bg: var(--primary-background-color);
|
||||
--input-active-bg: var(--ha-card-background);
|
||||
--input-color: var(--secondary-text-color);
|
||||
--input-active-color: var(--primary-text-color);
|
||||
--input-active-box-shadow: 0 2px 5px #ccc;
|
||||
--button-active-bg: var(--state-active-color);
|
||||
--button-active-color: var(--token-color-icon-primary);
|
||||
--outer-box-shadow: 0 4px 12px #111;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
import { mdiRadiator } from "@mdi/js";
|
||||
import { CSSResult, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import { fireEvent } from "../../../fire_event";
|
||||
|
||||
import {
|
||||
getPrinterEntityId,
|
||||
getPrinterSensorStateObj,
|
||||
getPrinterSwitchStateObj,
|
||||
} from "../../../helpers";
|
||||
import {
|
||||
AnycubicSpoolInfo,
|
||||
AnycubicSpoolInfoEntity,
|
||||
DomClickEvent,
|
||||
EvtTargSpoolEdit,
|
||||
HassEntity,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
} from "../../../types";
|
||||
|
||||
const SECONDARY_PREFIX = "secondary_";
|
||||
|
||||
const PRIMARY_ENTITY_ID_RUNOUT_REFILL = "ace_run_out_refill";
|
||||
const SECONDARY_ENTITY_ID_RUNOUT_REFILL =
|
||||
SECONDARY_PREFIX + PRIMARY_ENTITY_ID_RUNOUT_REFILL;
|
||||
const PRIMARY_ENTITY_ID_SPOOLS = "ace_spools";
|
||||
const SECONDARY_ENTITY_ID_SPOOLS = SECONDARY_PREFIX + PRIMARY_ENTITY_ID_SPOOLS;
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-multicolorbox_view")
|
||||
export class AnycubicPrintercardMulticolorboxview extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "printer-entities" })
|
||||
public printerEntities: HassEntityInfos;
|
||||
|
||||
@property({ attribute: "printer-entity-id-part" })
|
||||
public printerEntityIdPart: string | undefined;
|
||||
|
||||
@property()
|
||||
public box_id: number = 0;
|
||||
|
||||
@state()
|
||||
private _runoutRefillId: string = PRIMARY_ENTITY_ID_RUNOUT_REFILL;
|
||||
|
||||
@state()
|
||||
private _spoolsEntityId: string = PRIMARY_ENTITY_ID_SPOOLS;
|
||||
|
||||
@state()
|
||||
private spoolList: AnycubicSpoolInfo[] = [];
|
||||
|
||||
@state()
|
||||
private selectedIndex: number = -1;
|
||||
|
||||
@state()
|
||||
private selectedMaterialType: string = "";
|
||||
|
||||
@state()
|
||||
private selectedColor: number[] = [0, 0, 0];
|
||||
|
||||
@state()
|
||||
private _runoutRefillState: HassEntity | undefined;
|
||||
|
||||
@state()
|
||||
private _buttonRefill: string;
|
||||
|
||||
@state()
|
||||
private _buttonDry: string;
|
||||
|
||||
@state()
|
||||
private _changingRunout: boolean = false;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._buttonRefill = localize(
|
||||
"card.buttons.runout_refill",
|
||||
this.language,
|
||||
);
|
||||
this._buttonDry = localize("card.buttons.dry", this.language);
|
||||
}
|
||||
|
||||
if (changedProperties.has("box_id")) {
|
||||
if (this.box_id === 1) {
|
||||
this._runoutRefillId = SECONDARY_ENTITY_ID_RUNOUT_REFILL;
|
||||
this._spoolsEntityId = SECONDARY_ENTITY_ID_SPOOLS;
|
||||
} else {
|
||||
this._runoutRefillId = PRIMARY_ENTITY_ID_RUNOUT_REFILL;
|
||||
this._spoolsEntityId = PRIMARY_ENTITY_ID_SPOOLS;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("printerEntities") ||
|
||||
changedProperties.has("printerEntityIdPart")
|
||||
) {
|
||||
this.spoolList = (
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._spoolsEntityId,
|
||||
"not loaded",
|
||||
{ spool_info: [] },
|
||||
) as AnycubicSpoolInfoEntity
|
||||
).attributes.spool_info;
|
||||
this._runoutRefillState = getPrinterSwitchStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
this._runoutRefillId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="ac-printercard-mcbview">
|
||||
<div class="ac-printercard-mcbmenu ac-printercard-menuleft">
|
||||
<div class="ac-switch" @click=${this._handleRunoutRefillChanged}>
|
||||
<div class="ac-switch-label">${this._buttonRefill}</div>
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this._runoutRefillState}
|
||||
></ha-entity-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-printercard-spoolcont">${this._renderSpools()}</div>
|
||||
<div class="ac-printercard-mcbmenu ac-printercard-menuright">
|
||||
<ha-control-button @click=${this._openDryingModal}>
|
||||
<ha-svg-icon .path=${mdiRadiator}></ha-svg-icon>
|
||||
${this._buttonDry}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderSpools(): Generator<
|
||||
AnycubicSpoolInfo,
|
||||
void,
|
||||
LitTemplateResult
|
||||
> {
|
||||
return map(
|
||||
this.spoolList,
|
||||
(spool: AnycubicSpoolInfo, index: number): LitTemplateResult => {
|
||||
const ringStyle = {
|
||||
"background-color": spool.spool_loaded
|
||||
? `rgb(${spool.color[0]}, ${spool.color[1]}, ${spool.color[2]})`
|
||||
: "#aaa",
|
||||
};
|
||||
return html`
|
||||
<div
|
||||
class="ac-spool-info"
|
||||
.index=${index}
|
||||
.material_type=${spool.material_type}
|
||||
.color=${spool.color}
|
||||
@click=${this._editSpool}
|
||||
>
|
||||
<div class="ac-spool-color-ring-cont">
|
||||
<div
|
||||
class="ac-spool-color-ring-inner"
|
||||
style=${styleMap(ringStyle)}
|
||||
>
|
||||
<div class="ac-spool-color-num">${index + 1}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-spool-material-type">
|
||||
${spool.spool_loaded ? spool.material_type : "---"}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
) as Generator<AnycubicSpoolInfo, void, LitTemplateResult>;
|
||||
}
|
||||
|
||||
private _openDryingModal = (): void => {
|
||||
fireEvent(this, "ac-mcbdry-modal", {
|
||||
modalOpen: true,
|
||||
box_id: this.box_id,
|
||||
});
|
||||
};
|
||||
|
||||
private _handleRunoutRefillChanged = (_ev: Event): void => {
|
||||
// const refillActive = ev.target.checked;
|
||||
if (this._changingRunout) {
|
||||
return;
|
||||
}
|
||||
this._changingRunout = true;
|
||||
this.hass
|
||||
.callService("switch", "toggle", {
|
||||
entity_id: getPrinterEntityId(
|
||||
this.printerEntityIdPart,
|
||||
"switch",
|
||||
this._runoutRefillId,
|
||||
),
|
||||
})
|
||||
.then(() => {
|
||||
this._changingRunout = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingRunout = false;
|
||||
});
|
||||
};
|
||||
|
||||
private _editSpool = (ev: DomClickEvent<EvtTargSpoolEdit>): void => {
|
||||
const index: number = ev.currentTarget.index;
|
||||
const material_type: string = ev.currentTarget.material_type;
|
||||
const color: number[] = ev.currentTarget.color;
|
||||
fireEvent(this, "ac-mcb-modal", {
|
||||
modalOpen: true,
|
||||
box_id: this.box_id,
|
||||
spool_index: index,
|
||||
material_type: material_type,
|
||||
color: color,
|
||||
});
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-printercard-mcbview {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-printercard-mcbmenu {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 10.42%;
|
||||
}
|
||||
|
||||
.ac-printercard-spoolcont {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
width: 62.5%;
|
||||
}
|
||||
|
||||
.ac-spool-info {
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
width: 25%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.ac-spool-color-ring-cont {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ac-spool-color-ring-cont:before {
|
||||
content: "";
|
||||
display: block;
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.ac-spool-color-ring-inner {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
background-color: #aaa;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ac-spool-color-num {
|
||||
font-weight: 900;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
width: 46.5%;
|
||||
height: 46.5%;
|
||||
color: #222;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ac-spool-color-num:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
padding-top: 2.5px;
|
||||
}
|
||||
|
||||
.ac-spool-material-type {
|
||||
height: auto;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.ac-printercard-mcbmenu ha-control-button {
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-printercard-menuright ha-control-button {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.ac-printercard-mcbmenu .ac-switch-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ac-printercard-mcbmenu .ac-switch {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
text-align: center;
|
||||
margin: 0px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 4px;
|
||||
justify-content: center;
|
||||
background-color: #8686862e;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ac-printercard-mcbmenu .ac-switch:hover {
|
||||
background-color: #86868669;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
import { LitElement, PropertyValues, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import * as pkgjson from "../../../package.json";
|
||||
|
||||
import {
|
||||
AnycubicCardConfig,
|
||||
CustomCardsWindow,
|
||||
HassDevice,
|
||||
HassDeviceList,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
PrinterCardStatType,
|
||||
TemperatureUnit,
|
||||
} from "../../types";
|
||||
|
||||
import {
|
||||
getDefaultCardConfig,
|
||||
getPrinterDevices,
|
||||
getSelectedPrinter,
|
||||
undefinedDefault,
|
||||
} from "../../helpers";
|
||||
|
||||
import "./card/card.ts";
|
||||
import "./configure/configure.ts";
|
||||
|
||||
window.console.info(
|
||||
`%c KOBRAX-LAN-CARD %c v${pkgjson.version} `,
|
||||
"color: orange; font-weight: bold; background: black",
|
||||
"color: white; font-weight: bold; background: dimgray",
|
||||
);
|
||||
|
||||
const defaultConfig = getDefaultCardConfig();
|
||||
|
||||
@customElement("kobrax-lan-card-editor")
|
||||
export class AnycubicPrintercardEditor extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public config: AnycubicCardConfig = {};
|
||||
|
||||
@state()
|
||||
private printers?: HassDeviceList;
|
||||
|
||||
@state()
|
||||
private language: string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.printers = getPrinterDevices(this.hass);
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("hass") && this.hass.language !== this.language) {
|
||||
this.language = this.hass.language;
|
||||
}
|
||||
|
||||
if (changedProperties.has("config")) {
|
||||
this.config.vertical = undefinedDefault(
|
||||
this.config.vertical,
|
||||
defaultConfig.vertical,
|
||||
) as boolean;
|
||||
this.config.round = undefinedDefault(
|
||||
this.config.round,
|
||||
defaultConfig.round,
|
||||
) as boolean;
|
||||
this.config.use_24hr = undefinedDefault(
|
||||
this.config.use_24hr,
|
||||
defaultConfig.use_24hr,
|
||||
) as boolean;
|
||||
this.config.alwaysShow = undefinedDefault(
|
||||
this.config.alwaysShow,
|
||||
defaultConfig.alwaysShow,
|
||||
) as boolean;
|
||||
this.config.showSettingsButton = undefinedDefault(
|
||||
this.config.showSettingsButton,
|
||||
defaultConfig.showSettingsButton,
|
||||
) as boolean;
|
||||
this.config.temperatureUnit = undefinedDefault(
|
||||
this.config.temperatureUnit,
|
||||
defaultConfig.temperatureUnit,
|
||||
) as TemperatureUnit;
|
||||
this.config.monitoredStats = undefinedDefault(
|
||||
this.config.monitoredStats,
|
||||
defaultConfig.monitoredStats,
|
||||
) as PrinterCardStatType[];
|
||||
this.config.slotColors = undefinedDefault(
|
||||
this.config.slotColors,
|
||||
defaultConfig.slotColors,
|
||||
) as string[];
|
||||
this.config.scaleFactor = undefinedDefault(
|
||||
this.config.scaleFactor,
|
||||
defaultConfig.scaleFactor,
|
||||
) as number;
|
||||
}
|
||||
}
|
||||
|
||||
public setConfig(config: AnycubicCardConfig): void {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<anycubic-printercard-configure
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.printers=${this.printers}
|
||||
.cardConfig=${this.config}
|
||||
></anycubic-printercard-configure>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("kobrax-lan-card")
|
||||
export class AnycubicCard extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public config: AnycubicCardConfig = {};
|
||||
|
||||
@state()
|
||||
private printers?: HassDeviceList;
|
||||
|
||||
@state()
|
||||
private language: string;
|
||||
|
||||
@state()
|
||||
private selectedPrinterID: string | undefined;
|
||||
|
||||
@state()
|
||||
private selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@state()
|
||||
private vertical?: boolean;
|
||||
|
||||
@state()
|
||||
private round?: boolean;
|
||||
|
||||
@state()
|
||||
private use_24hr?: boolean;
|
||||
|
||||
@state()
|
||||
private showSettingsButton?: boolean;
|
||||
|
||||
@state()
|
||||
private alwaysShow?: boolean;
|
||||
|
||||
@state()
|
||||
private temperatureUnit: TemperatureUnit | undefined;
|
||||
|
||||
@state()
|
||||
private lightEntityId?: string | undefined;
|
||||
|
||||
@state()
|
||||
private powerEntityId?: string | undefined;
|
||||
|
||||
@state()
|
||||
private cameraEntityId?: string | undefined;
|
||||
|
||||
@state()
|
||||
private scaleFactor?: number | undefined;
|
||||
|
||||
@state()
|
||||
private slotColors?: string[];
|
||||
|
||||
@state()
|
||||
private monitoredStats: PrinterCardStatType[] | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.printers = getPrinterDevices(this.hass);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("hass") && this.hass.language !== this.language) {
|
||||
this.language = this.hass.language;
|
||||
}
|
||||
|
||||
if (changedProperties.has("config") || changedProperties.has("printers")) {
|
||||
this.vertical = undefinedDefault(
|
||||
this.config.vertical,
|
||||
defaultConfig.vertical,
|
||||
) as boolean;
|
||||
this.round = undefinedDefault(
|
||||
this.config.round,
|
||||
defaultConfig.round,
|
||||
) as boolean;
|
||||
this.use_24hr = undefinedDefault(
|
||||
this.config.use_24hr,
|
||||
defaultConfig.use_24hr,
|
||||
) as boolean;
|
||||
this.alwaysShow = undefinedDefault(
|
||||
this.config.alwaysShow,
|
||||
defaultConfig.alwaysShow,
|
||||
) as boolean;
|
||||
this.showSettingsButton = undefinedDefault(
|
||||
this.config.showSettingsButton,
|
||||
defaultConfig.showSettingsButton,
|
||||
) as boolean;
|
||||
this.temperatureUnit = undefinedDefault(
|
||||
this.config.temperatureUnit,
|
||||
defaultConfig.temperatureUnit,
|
||||
) as TemperatureUnit;
|
||||
this.lightEntityId = this.config.lightEntityId;
|
||||
this.powerEntityId = this.config.powerEntityId;
|
||||
this.cameraEntityId = this.config.cameraEntityId;
|
||||
this.scaleFactor = this.config.scaleFactor;
|
||||
this.slotColors = this.config.slotColors;
|
||||
this.monitoredStats = this.config.monitoredStats;
|
||||
if (this.config.printer_id && this.printers) {
|
||||
this.selectedPrinterID = this.config.printer_id;
|
||||
this.selectedPrinterDevice = getSelectedPrinter(
|
||||
this.printers,
|
||||
this.config.printer_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setConfig(config: AnycubicCardConfig): void {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<anycubic-printercard-card
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.monitoredStats=${this.config.monitoredStats}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
.vertical=${this.vertical}
|
||||
.round=${this.round}
|
||||
.use_24hr=${this.use_24hr}
|
||||
.showSettingsButton=${this.showSettingsButton}
|
||||
.alwaysShow=${this.alwaysShow}
|
||||
.temperatureUnit=${this.temperatureUnit}
|
||||
.lightEntityId=${this.lightEntityId}
|
||||
.powerEntityId=${this.powerEntityId}
|
||||
.cameraEntityId=${this.cameraEntityId}
|
||||
.scaleFactor=${this.scaleFactor}
|
||||
.slotColors=${this.slotColors}
|
||||
></anycubic-printercard-card>
|
||||
`;
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return 2;
|
||||
}
|
||||
|
||||
static getConfigElement(): HTMLElement {
|
||||
return document.createElement("kobrax-lan-card-editor");
|
||||
}
|
||||
|
||||
static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
_entities: string[],
|
||||
_entitiesFallback: string[],
|
||||
): AnycubicCardConfig {
|
||||
return { printer_id: Object.keys(getPrinterDevices(hass))[0] };
|
||||
}
|
||||
}
|
||||
|
||||
const customCardsWindow = window as CustomCardsWindow;
|
||||
|
||||
customCardsWindow.customCards = customCardsWindow.customCards || [];
|
||||
customCardsWindow.customCards.push({
|
||||
type: "kobrax-lan-card",
|
||||
name: "Kobrax LAN Card",
|
||||
preview: true,
|
||||
description: "Kobrax LAN Integration Card",
|
||||
});
|
||||
@@ -0,0 +1,396 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { query } from "lit/decorators/query.js";
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller.js";
|
||||
import { animate, Options as motionOptions } from "@lit-labs/motion";
|
||||
|
||||
import { getDimensions } from "./utils";
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import {
|
||||
getPrinterImageStateUrl,
|
||||
getPrinterSensorStateObj,
|
||||
isPrintStatePrinting,
|
||||
updateElementStyleWithObject,
|
||||
} from "../../../helpers";
|
||||
|
||||
import {
|
||||
AnimatedPrinterConfig,
|
||||
AnimatedPrinterDimensions,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
} from "../../../types";
|
||||
|
||||
const animOptionsGantry: motionOptions = {
|
||||
keyframeOptions: {
|
||||
duration: 2000,
|
||||
direction: "alternate",
|
||||
composite: "add",
|
||||
},
|
||||
properties: ["left"],
|
||||
};
|
||||
|
||||
const animOptionsAxis: motionOptions = {
|
||||
keyframeOptions: {
|
||||
duration: 100,
|
||||
composite: "add",
|
||||
},
|
||||
properties: ["top"],
|
||||
};
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-animated_printer")
|
||||
export class AnycubicPrintercardAnimatedPrinter extends LitElement {
|
||||
@query(".ac-printercard-animatedprinter")
|
||||
private _rootElement: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-scalable")
|
||||
private _elAcAPr_scalable: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-frame")
|
||||
private _elAcAPr_frame: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-hole")
|
||||
private _elAcAPr_hole: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-buildarea")
|
||||
private _elAcAPr_buildarea: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-animprint")
|
||||
private _elAcAPr_animprint: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-buildplate")
|
||||
private _elAcAPr_buildplate: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-xaxis")
|
||||
private _elAcAPr_xaxis: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-gantry")
|
||||
private _elAcAPr_gantry: HTMLElement | undefined;
|
||||
|
||||
@query(".ac-apr-nozzle")
|
||||
private _elAcAPr_nozzle: HTMLElement | undefined;
|
||||
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "scale-factor" })
|
||||
public scaleFactor?: number;
|
||||
|
||||
@property({ attribute: "printer-config" })
|
||||
public printerConfig: AnimatedPrinterConfig;
|
||||
|
||||
@property({ attribute: "printer-entities" })
|
||||
public printerEntities: HassEntityInfos;
|
||||
|
||||
@property({ attribute: "printer-entity-id-part" })
|
||||
public printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
private dimensions: AnimatedPrinterDimensions | undefined;
|
||||
|
||||
@state()
|
||||
private resizeObserver: ResizeController | undefined;
|
||||
|
||||
@state()
|
||||
private _progressNum: number = 0;
|
||||
|
||||
@state()
|
||||
private animKeyframeGantry: number = 0;
|
||||
|
||||
@state()
|
||||
private _isPrinting: boolean = false;
|
||||
|
||||
@state()
|
||||
private imagePreviewUrl: string | undefined;
|
||||
|
||||
@state()
|
||||
private imagePreviewBgUrl: string | undefined;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.resizeObserver = new ResizeController(this, {
|
||||
callback: this._onResizeEvent,
|
||||
});
|
||||
|
||||
if (this.dimensions && this._isPrinting) {
|
||||
this._moveGantry();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("scaleFactor")) {
|
||||
this._onResizeEvent();
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("printerEntities") ||
|
||||
changedProperties.has("printerEntityIdPart")
|
||||
) {
|
||||
const prevUrl = getPrinterImageStateUrl(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_preview",
|
||||
);
|
||||
if (this.imagePreviewUrl !== prevUrl) {
|
||||
this.imagePreviewUrl = prevUrl;
|
||||
this.imagePreviewBgUrl = this.imagePreviewUrl
|
||||
? `url('${prevUrl}')`
|
||||
: undefined;
|
||||
}
|
||||
this._progressNum =
|
||||
Number(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_progress",
|
||||
0,
|
||||
).state,
|
||||
) / 100;
|
||||
const printingState = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_state",
|
||||
).state.toLowerCase();
|
||||
|
||||
const newIsPrinting = isPrintStatePrinting(printingState);
|
||||
|
||||
if (this.dimensions && !this._isPrinting && newIsPrinting) {
|
||||
this._moveGantry();
|
||||
}
|
||||
|
||||
this._isPrinting = newIsPrinting;
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
|
||||
if (
|
||||
(changedProperties.has("dimensions") ||
|
||||
changedProperties.has("animKeyframeGantry") ||
|
||||
changedProperties.has("hass")) &&
|
||||
this.dimensions
|
||||
) {
|
||||
const progY = this._progressNum * -1 * this.dimensions.BuildArea.height;
|
||||
updateElementStyleWithObject(this._elAcAPr_xaxis, {
|
||||
...this.dimensions.XAxis,
|
||||
top: this.dimensions.XAxis.top + progY,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_gantry, {
|
||||
...this.dimensions.Gantry,
|
||||
left:
|
||||
this.animKeyframeGantry !== 0
|
||||
? this.dimensions.Gantry.left + this.dimensions.BuildPlate.width
|
||||
: this.dimensions.Gantry.left,
|
||||
top: this.dimensions.Gantry.top + progY,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_animprint, {
|
||||
height: `${this._progressNum * 100}%`,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (changedProperties.has("dimensions") && this.dimensions) {
|
||||
updateElementStyleWithObject(this._elAcAPr_scalable, {
|
||||
...this.dimensions.Scalable,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_frame, {
|
||||
...this.dimensions.Frame,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_hole, {
|
||||
...this.dimensions.Hole,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_buildarea, {
|
||||
...this.dimensions.BuildArea,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_buildplate, {
|
||||
...this.dimensions.BuildPlate,
|
||||
});
|
||||
updateElementStyleWithObject(this._elAcAPr_nozzle, {
|
||||
...this.dimensions.Nozzle,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesPreview = {
|
||||
"background-image": this.imagePreviewBgUrl,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="ac-printercard-animatedprinter">
|
||||
${this.dimensions
|
||||
? html` <div class="ac-apr-scalable">
|
||||
<div class="ac-apr-frame">
|
||||
<div class="ac-apr-hole"></div>
|
||||
</div>
|
||||
<div class="ac-apr-buildarea">
|
||||
<div class="ac-apr-animprint">
|
||||
${this.imagePreviewBgUrl
|
||||
? html`
|
||||
<div
|
||||
class="ac-apr-imgprev"
|
||||
style=${styleMap(stylesPreview)}
|
||||
></div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-apr-buildplate"></div>
|
||||
<div
|
||||
class="ac-apr-xaxis"
|
||||
${animate({ ...animOptionsAxis })}
|
||||
></div>
|
||||
<div
|
||||
class="ac-apr-gantry"
|
||||
${animate({ ...animOptionsAxis })}
|
||||
${animate(this._gantryAnimOptions)}
|
||||
>
|
||||
<div class="ac-apr-nozzle"></div>
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _gantryAnimOptions = (): motionOptions => {
|
||||
return {
|
||||
...animOptionsGantry,
|
||||
onComplete: this._moveGantry,
|
||||
disabled: !(this.dimensions && this._isPrinting),
|
||||
};
|
||||
};
|
||||
|
||||
private _onResizeEvent = (): void => {
|
||||
if (this._rootElement) {
|
||||
const height: number = this._rootElement.clientHeight;
|
||||
const width: number = this._rootElement.clientWidth;
|
||||
this._setDimensions(width, height);
|
||||
}
|
||||
};
|
||||
|
||||
private _setDimensions(width: number, height: number): void {
|
||||
this.dimensions = getDimensions(
|
||||
this.printerConfig,
|
||||
{ width, height },
|
||||
this.scaleFactor || 1.0,
|
||||
);
|
||||
}
|
||||
|
||||
private _moveGantry = (): void => {
|
||||
this.animKeyframeGantry = this._isPrinting
|
||||
? Number(!this.animKeyframeGantry)
|
||||
: 0;
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ac-printercard-animatedprinter {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ac-apr-scalable {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ac-apr-frame {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
border-radius: 8px;
|
||||
background-color: #bbbbbb;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ac-apr-hole {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
background-color: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ac-apr-buildarea {
|
||||
background-color: rgba(0, 0, 0, 0.075);
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ac-apr-buildplate {
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
background-color: #333333;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.ac-apr-xaxis {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
background-color: #aaaaaa;
|
||||
}
|
||||
|
||||
.ac-apr-animprint {
|
||||
background-color: var(--primary-text-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-apr-imgprev {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: 100%;
|
||||
}
|
||||
|
||||
.ac-apr-gantry {
|
||||
background-color: #333333;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ac-apr-nozzle {
|
||||
background-color: #aaaaaa;
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
clip-path: polygon(100% 0, 100% 50%, 50% 75%, 0 50%, 0 0);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { CSSResult, LitElement, css, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import { printerConfigAnycubic } from "./utils";
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import {
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
} from "../../../types";
|
||||
|
||||
import "./animated_printer.ts";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-printer_view")
|
||||
export class AnycubicPrintercardPrinterview extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "toggle-video", type: Function })
|
||||
public toggleVideo?: () => void;
|
||||
|
||||
@property({ attribute: "printer-entities" })
|
||||
public printerEntities: HassEntityInfos;
|
||||
|
||||
@property({ attribute: "printer-entity-id-part" })
|
||||
public printerEntityIdPart: string | undefined;
|
||||
|
||||
@property({ attribute: "scale-factor" })
|
||||
public scaleFactor?: number;
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="ac-printercard-printerview" @click=${this._viewClick}>
|
||||
<anycubic-printercard-animated_printer
|
||||
.hass=${this.hass}
|
||||
.scaleFactor=${this.scaleFactor}
|
||||
.printerEntities=${this.printerEntities}
|
||||
.printerEntityIdPart=${this.printerEntityIdPart}
|
||||
.printerConfig=${printerConfigAnycubic}
|
||||
></anycubic-printercard-animated_printer>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _viewClick = (): void => {
|
||||
if (this.toggleVideo) {
|
||||
this.toggleVideo();
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-printercard-printerview {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import {
|
||||
AnimatedPrinterBasicDimension,
|
||||
AnimatedPrinterConfig,
|
||||
AnimatedPrinterDimensions,
|
||||
} from "../../../types";
|
||||
|
||||
class Scale {
|
||||
scale_factor: number;
|
||||
|
||||
constructor(scale_factor: number) {
|
||||
this.scale_factor = scale_factor;
|
||||
}
|
||||
|
||||
val(value): number {
|
||||
return this.scale_factor * value;
|
||||
}
|
||||
|
||||
og(value): number {
|
||||
return value / this.scale_factor;
|
||||
}
|
||||
|
||||
scaleFactor(): number {
|
||||
return this.scale_factor;
|
||||
}
|
||||
}
|
||||
|
||||
export function getDimensions(
|
||||
config: AnimatedPrinterConfig,
|
||||
bounds: AnimatedPrinterBasicDimension,
|
||||
haScaleFactor: number,
|
||||
): AnimatedPrinterDimensions {
|
||||
/* We estimate the initial scale factor based on the height + width of the frame, then compound with set factor */
|
||||
const scaledBoundsHeight =
|
||||
bounds.height /
|
||||
(config.top.height + config.bottom.height + config.left.height);
|
||||
|
||||
const scaledBoundsWidth =
|
||||
bounds.width / (config.top.width + config.left.width + config.right.width);
|
||||
|
||||
const scale = new Scale(
|
||||
Math.min(scaledBoundsHeight, scaledBoundsWidth) * haScaleFactor,
|
||||
);
|
||||
|
||||
/* Frame */
|
||||
const F_W = scale.val(config.top.width); // Width
|
||||
const F_H = scale.val(
|
||||
config.top.height + config.bottom.height + config.left.height,
|
||||
); // Height
|
||||
|
||||
/* Scalable */
|
||||
// const S_ML = (bounds.width - F_W) / 2; // Margin Left
|
||||
// const S_MT = (bounds.height - F_H) / 2; // Margin Top
|
||||
|
||||
/* Hole */
|
||||
const H_W = scale.val(
|
||||
config.top.width - (config.left.width + config.right.width),
|
||||
); // Width
|
||||
const H_H = scale.val(config.left.height); // Height
|
||||
const H_L = scale.val(config.left.width); // Left
|
||||
const H_T = scale.val(config.top.height); // Top
|
||||
|
||||
/* Basis */
|
||||
const BASIS_Y =
|
||||
scale.val(config.top.height - config.buildplate.verticalOffset) + H_H;
|
||||
const BASIS_X =
|
||||
BASIS_Y +
|
||||
scale.val(
|
||||
(config.xAxis.extruder.height - config.xAxis.height) / 2 -
|
||||
(config.xAxis.extruder.height + 12),
|
||||
);
|
||||
|
||||
/* Build Area */
|
||||
const B_W = scale.val(config.buildplate.maxWidth); // Width
|
||||
const B_H = scale.val(config.buildplate.maxHeight); // Height
|
||||
const B_L = scale.val(
|
||||
config.left.width + (scale.og(H_W) - config.buildplate.maxWidth) / 2,
|
||||
); // Left
|
||||
const B_T = BASIS_Y - scale.val(config.buildplate.maxHeight); // Top
|
||||
|
||||
/* Build Plate */
|
||||
const P_W = B_W; // Width
|
||||
const P_L = B_L; // Left
|
||||
const P_T = BASIS_Y; // Top
|
||||
|
||||
/* X Axis */
|
||||
const X_W = scale.val(config.xAxis.width);
|
||||
const X_H = scale.val(config.xAxis.height);
|
||||
const X_L = scale.val(config.xAxis.offsetLeft);
|
||||
|
||||
/* Track */
|
||||
const T_W = X_W;
|
||||
const T_H = X_H;
|
||||
|
||||
/* Extruder */
|
||||
const E_W = scale.val(config.xAxis.extruder.width);
|
||||
const E_H = scale.val(config.xAxis.extruder.height);
|
||||
const E_L = P_L - E_W / 2;
|
||||
const E_M = E_L + B_W;
|
||||
|
||||
/* Nozzle */
|
||||
const N_W = scale.val(12);
|
||||
const N_H = scale.val(12);
|
||||
const N_L = (E_W - N_W) / 2;
|
||||
const N_T = E_H;
|
||||
|
||||
const E_T = P_T - E_H - N_H;
|
||||
const X_T = E_T + E_H * 0.7 - X_H / 2;
|
||||
|
||||
return {
|
||||
Scalable: {
|
||||
width: F_W,
|
||||
height: F_H,
|
||||
},
|
||||
Frame: {
|
||||
width: F_W,
|
||||
height: F_H,
|
||||
},
|
||||
Hole: {
|
||||
width: H_W,
|
||||
height: H_H,
|
||||
left: H_L,
|
||||
top: H_T,
|
||||
},
|
||||
BuildArea: {
|
||||
width: B_W,
|
||||
height: B_H,
|
||||
left: B_L,
|
||||
top: B_T,
|
||||
},
|
||||
BuildPlate: {
|
||||
width: P_W,
|
||||
left: P_L,
|
||||
top: P_T,
|
||||
},
|
||||
XAxis: {
|
||||
width: X_W,
|
||||
height: X_H,
|
||||
left: X_L,
|
||||
top: X_T,
|
||||
},
|
||||
Track: {
|
||||
width: T_W,
|
||||
height: T_H,
|
||||
},
|
||||
Basis: {
|
||||
Y: BASIS_Y,
|
||||
X: BASIS_X,
|
||||
},
|
||||
Gantry: {
|
||||
width: E_W,
|
||||
height: E_H,
|
||||
left: E_L,
|
||||
top: E_T,
|
||||
},
|
||||
Nozzle: {
|
||||
width: N_W,
|
||||
height: N_H,
|
||||
left: N_L,
|
||||
top: N_T,
|
||||
},
|
||||
GantryMaxLeft: E_M,
|
||||
};
|
||||
}
|
||||
|
||||
export const printerConfigAnycubic: AnimatedPrinterConfig = {
|
||||
top: {
|
||||
width: 340,
|
||||
height: 20,
|
||||
},
|
||||
bottom: {
|
||||
width: 340,
|
||||
height: 52.3,
|
||||
},
|
||||
left: {
|
||||
width: 30,
|
||||
height: 400,
|
||||
},
|
||||
right: {
|
||||
width: 30,
|
||||
height: 380,
|
||||
},
|
||||
|
||||
buildplate: {
|
||||
maxWidth: 250,
|
||||
maxHeight: 260,
|
||||
verticalOffset: 55,
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
stepper: true,
|
||||
width: 400,
|
||||
offsetLeft: -30,
|
||||
height: 30,
|
||||
extruder: {
|
||||
width: 60,
|
||||
height: 100,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,961 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { animate, Options as motionOptions } from "@lit-labs/motion";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import { platform } from "../../../const";
|
||||
import { HASSDomEvent } from "../../../fire_event";
|
||||
|
||||
import {
|
||||
getPrinterEntityId,
|
||||
getPrinterSensorStateObj,
|
||||
isFDMPrinter,
|
||||
speedModesFromStateObj,
|
||||
} from "../../../helpers";
|
||||
|
||||
import {
|
||||
AnycubicPrintOptionConfirmationType,
|
||||
AnycubicSpeedModeEntity,
|
||||
AnycubicTargetTempEntity,
|
||||
DomClickEvent,
|
||||
DropdownEvent,
|
||||
EvtTargConfirmationMode,
|
||||
HassDevice,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
ModalEventBase,
|
||||
SelectDropdownProps,
|
||||
TextfieldChangeDetail,
|
||||
} from "../../../types";
|
||||
|
||||
import { commonModalStyle } from "../../ui/modal-styles";
|
||||
|
||||
import "../../ui/select-dropdown.ts";
|
||||
|
||||
const animOptionsCard: motionOptions = {
|
||||
keyframeOptions: {
|
||||
duration: 250,
|
||||
direction: "alternate",
|
||||
easing: "ease-in-out",
|
||||
},
|
||||
properties: ["height", "opacity", "scale"],
|
||||
};
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-printsettings_modal")
|
||||
export class AnycubicPrintercardPrintsettingsModal extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@property({ attribute: "printer-entities" })
|
||||
public printerEntities: HassEntityInfos;
|
||||
|
||||
@property({ attribute: "printer-entity-id-part" })
|
||||
public printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
private availableSpeedModes: SelectDropdownProps = {};
|
||||
|
||||
@state()
|
||||
private isFDM: boolean = false;
|
||||
|
||||
@state()
|
||||
private currentSpeedModeKey: number = 0;
|
||||
|
||||
@state()
|
||||
private currentSpeedModeDescr: string | undefined = undefined;
|
||||
|
||||
@state()
|
||||
private _userEditSpeedMode: boolean = false;
|
||||
|
||||
@state()
|
||||
private currentFanSpeed: number = 0;
|
||||
|
||||
@state()
|
||||
private _userEditFanSpeed: boolean = false;
|
||||
|
||||
@state()
|
||||
private currentAuxFanSpeed: number = 0;
|
||||
|
||||
@state()
|
||||
private _userEditAuxFanSpeed: boolean = false;
|
||||
|
||||
@state()
|
||||
private currentBoxFanSpeed: number = 0;
|
||||
|
||||
@state()
|
||||
private _userEditBoxFanSpeed: boolean = false;
|
||||
|
||||
@state()
|
||||
private currentTargetTempNozzle: number = 0;
|
||||
|
||||
@state()
|
||||
private minTargetTempNozzle: number = 0;
|
||||
|
||||
@state()
|
||||
private maxTargetTempNozzle: number = 0;
|
||||
|
||||
@state()
|
||||
private _userEditTargetTempNozzle: boolean = false;
|
||||
|
||||
@state()
|
||||
private currentTargetTempHotbed: number = 0;
|
||||
|
||||
@state()
|
||||
private minTargetTempHotbed: number = 0;
|
||||
|
||||
@state()
|
||||
private maxTargetTempHotbed: number = 0;
|
||||
|
||||
@state()
|
||||
private _userEditTargetTempHotbed: boolean = false;
|
||||
|
||||
@state()
|
||||
private _confirmationType: AnycubicPrintOptionConfirmationType | undefined;
|
||||
|
||||
@state()
|
||||
private _isOpen: boolean = false;
|
||||
|
||||
@state()
|
||||
private _confirmMessage: string;
|
||||
|
||||
@state()
|
||||
private _labelNozzleTemperature: string;
|
||||
|
||||
@state()
|
||||
private _labelHotbedTemperature: string;
|
||||
|
||||
@state()
|
||||
private _labelFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _labelAuxFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _labelBoxFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _buttonYes: string;
|
||||
|
||||
@state()
|
||||
private _buttonNo: string;
|
||||
|
||||
@state()
|
||||
private _buttonPrintPause: string;
|
||||
|
||||
@state()
|
||||
private _buttonPrintResume: string;
|
||||
|
||||
@state()
|
||||
private _buttonPrintCancel: string;
|
||||
|
||||
@state()
|
||||
private _buttonSaveSpeedMode: string;
|
||||
|
||||
@state()
|
||||
private _buttonSaveTargetNozzle: string;
|
||||
|
||||
@state()
|
||||
private _buttonSaveTargetHotbed: string;
|
||||
|
||||
@state()
|
||||
private _buttonSaveFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _buttonSaveAuxFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _buttonSaveBoxFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _changingSettings: boolean = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.addEventListener("ac-select-dropdown", this._handleDropdownEvent);
|
||||
this.addEventListener("click", (e) => {
|
||||
this._closeModal(e);
|
||||
});
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.parentElement?.addEventListener(
|
||||
"ac-printset-modal",
|
||||
this._handleModalEvent,
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
this.parentElement?.removeEventListener(
|
||||
"ac-printset-modal",
|
||||
this._handleModalEvent,
|
||||
);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._labelNozzleTemperature = localize(
|
||||
"card.print_settings.label_nozzle_temp",
|
||||
this.language,
|
||||
);
|
||||
this._labelHotbedTemperature = localize(
|
||||
"card.print_settings.label_hotbed_temp",
|
||||
this.language,
|
||||
);
|
||||
this._labelFanSpeed = localize(
|
||||
"card.print_settings.label_fan_speed",
|
||||
this.language,
|
||||
);
|
||||
this._labelAuxFanSpeed = localize(
|
||||
"card.print_settings.label_aux_fan_speed",
|
||||
this.language,
|
||||
);
|
||||
this._labelBoxFanSpeed = localize(
|
||||
"card.print_settings.label_box_fan_speed",
|
||||
this.language,
|
||||
);
|
||||
this._buttonYes = localize("common.actions.yes", this.language);
|
||||
this._buttonNo = localize("common.actions.no", this.language);
|
||||
this._buttonPrintPause = localize(
|
||||
"card.print_settings.print_pause",
|
||||
this.language,
|
||||
);
|
||||
this._buttonPrintResume = localize(
|
||||
"card.print_settings.print_resume",
|
||||
this.language,
|
||||
);
|
||||
this._buttonPrintCancel = localize(
|
||||
"card.print_settings.print_cancel",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSaveSpeedMode = localize(
|
||||
"card.print_settings.save_speed_mode",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSaveTargetNozzle = localize(
|
||||
"card.print_settings.save_target_nozzle",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSaveTargetHotbed = localize(
|
||||
"card.print_settings.save_target_hotbed",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSaveFanSpeed = localize(
|
||||
"card.print_settings.save_fan_speed",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSaveAuxFanSpeed = localize(
|
||||
"card.print_settings.save_aux_fan_speed",
|
||||
this.language,
|
||||
);
|
||||
this._buttonSaveBoxFanSpeed = localize(
|
||||
"card.print_settings.save_box_fan_speed",
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("printerEntities") ||
|
||||
changedProperties.has("printerEntityIdPart")
|
||||
) {
|
||||
this.isFDM = isFDMPrinter(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
);
|
||||
if (!this._userEditFanSpeed) {
|
||||
this.currentFanSpeed = Number(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"fan_speed",
|
||||
0,
|
||||
).state,
|
||||
);
|
||||
}
|
||||
if (!this._userEditTargetTempNozzle) {
|
||||
const currentTargetTempNozzleState: AnycubicTargetTempEntity =
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"target_nozzle_temperature",
|
||||
0,
|
||||
{ limit_min: 0, limit_max: 0 },
|
||||
) as AnycubicTargetTempEntity;
|
||||
this.currentTargetTempNozzle = Number(
|
||||
currentTargetTempNozzleState.state,
|
||||
);
|
||||
this.minTargetTempNozzle =
|
||||
currentTargetTempNozzleState.attributes.limit_min;
|
||||
this.maxTargetTempNozzle =
|
||||
currentTargetTempNozzleState.attributes.limit_max;
|
||||
}
|
||||
if (!this._userEditTargetTempHotbed) {
|
||||
const currentTargetTempHotbedState: AnycubicTargetTempEntity =
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"target_hotbed_temperature",
|
||||
0,
|
||||
{ limit_min: 0, limit_max: 0 },
|
||||
) as AnycubicTargetTempEntity;
|
||||
this.currentTargetTempHotbed = Number(
|
||||
currentTargetTempHotbedState.state,
|
||||
);
|
||||
this.minTargetTempHotbed =
|
||||
currentTargetTempHotbedState.attributes.limit_min;
|
||||
this.maxTargetTempHotbed =
|
||||
currentTargetTempHotbedState.attributes.limit_max;
|
||||
}
|
||||
|
||||
if (!this._userEditSpeedMode) {
|
||||
const speedModeState: AnycubicSpeedModeEntity =
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_speed_mode",
|
||||
"",
|
||||
{ available_modes: [], job_speed_mode_code: -1 },
|
||||
) as AnycubicSpeedModeEntity;
|
||||
this.availableSpeedModes = speedModesFromStateObj(
|
||||
speedModeState,
|
||||
) as SelectDropdownProps;
|
||||
this.currentSpeedModeKey =
|
||||
speedModeState.attributes.print_speed_mode_code;
|
||||
this.currentSpeedModeDescr =
|
||||
this.currentSpeedModeKey >= 0 &&
|
||||
this.currentSpeedModeKey in this.availableSpeedModes
|
||||
? this.availableSpeedModes[this.currentSpeedModeKey]
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues<this>): void {
|
||||
super.update(changedProperties);
|
||||
if (this._isOpen) {
|
||||
this.style.display = "block";
|
||||
} else {
|
||||
this.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesMain = {
|
||||
height: "auto",
|
||||
opacity: 1.0,
|
||||
scale: 1.0,
|
||||
};
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="ac-modal-container"
|
||||
style=${styleMap(stylesMain)}
|
||||
${animate({ ...animOptionsCard })}
|
||||
>
|
||||
<span class="ac-modal-close" @click=${this._closeModal}>×</span>
|
||||
<div class="ac-modal-card" @click=${this._cardClick}>
|
||||
${this._renderCard()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_renderCard(): LitTemplateResult {
|
||||
return this._confirmationType
|
||||
? this._renderConfirm()
|
||||
: this._renderSettings();
|
||||
}
|
||||
|
||||
_renderConfirm(): LitTemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
<div class="ac-settings-header">Confirm Action</div>
|
||||
<div>
|
||||
<div class="ac-confirm-description">${this._confirmMessage}</div>
|
||||
<div class="ac-confirm-buttons">
|
||||
<ha-control-button
|
||||
@click=${this._handleConfirmApprove}
|
||||
.disabled=${this._changingSettings}
|
||||
>
|
||||
${this._buttonYes}
|
||||
</ha-control-button>
|
||||
<ha-control-button @click=${this._handleConfirmCancel}>
|
||||
${this._buttonNo}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_renderSettings(): LitTemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
<div class="ac-settings-header">Print Settings</div>
|
||||
<div>
|
||||
<div class="ac-settings-row ac-settings-buttonrow">
|
||||
<ha-control-button
|
||||
.confirmation_type=${AnycubicPrintOptionConfirmationType.PAUSE}
|
||||
@click=${this._setConfirmationMode}
|
||||
>
|
||||
${this._buttonPrintPause}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row ac-settings-buttonrow">
|
||||
<ha-control-button
|
||||
.confirmation_type=${AnycubicPrintOptionConfirmationType.RESUME}
|
||||
@click=${this._setConfirmationMode}
|
||||
>
|
||||
${this._buttonPrintResume}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row ac-settings-buttonrow">
|
||||
<ha-control-button
|
||||
.confirmation_type=${AnycubicPrintOptionConfirmationType.CANCEL}
|
||||
@click=${this._setConfirmationMode}
|
||||
>
|
||||
${this._buttonPrintCancel}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
${this.isFDM
|
||||
? html`
|
||||
<div class="ac-settings-row">
|
||||
<anycubic-ui-select-dropdown
|
||||
.availableOptions=${this.availableSpeedModes}
|
||||
.placeholder=${this.currentSpeedModeDescr}
|
||||
.initialItem=${this.currentSpeedModeDescr}
|
||||
></anycubic-ui-select-dropdown>
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSettings}
|
||||
@click=${this._handleSaveSpeedModeButton}
|
||||
>
|
||||
${this._buttonSaveSpeedMode}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row">
|
||||
<ha-textfield
|
||||
.value=${this.currentTargetTempNozzle}
|
||||
.placeholder=${this.currentTargetTempNozzle}
|
||||
.label=${this._labelNozzleTemperature}
|
||||
.type=${"number"}
|
||||
.min=${this.minTargetTempNozzle}
|
||||
.max=${this.maxTargetTempNozzle}
|
||||
@input=${this._handleTargetTempNozzleChange}
|
||||
@keydown=${this._handleTargetTempNozzleKeyDown}
|
||||
></ha-textfield>
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSettings}
|
||||
@click=${this._handleSaveTargetTempNozzleButton}
|
||||
>
|
||||
${this._buttonSaveTargetNozzle}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row">
|
||||
<ha-textfield
|
||||
.value=${this.currentTargetTempHotbed}
|
||||
.placeholder=${this.currentTargetTempHotbed}
|
||||
.label=${this._labelHotbedTemperature}
|
||||
.type=${"number"}
|
||||
.min=${this.minTargetTempHotbed}
|
||||
.max=${this.maxTargetTempHotbed}
|
||||
@input=${this._handleTargetTempHotbedChange}
|
||||
@keydown=${this._handleTargetTempHotbedKeyDown}
|
||||
></ha-textfield>
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSettings}
|
||||
@click=${this._handleSaveTargetTempHotbedButton}
|
||||
>
|
||||
${this._buttonSaveTargetHotbed}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row">
|
||||
<ha-textfield
|
||||
.value=${this.currentFanSpeed}
|
||||
.placeholder=${this.currentFanSpeed}
|
||||
.label=${this._labelFanSpeed}
|
||||
.type=${"number"}
|
||||
.min=${0}
|
||||
.max=${100}
|
||||
@input=${this._handleFanSpeedChange}
|
||||
@keydown=${this._handleFanSpeedKeyDown}
|
||||
></ha-textfield>
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSettings}
|
||||
@click=${this._handleSaveFanSpeedButton}
|
||||
>
|
||||
${this._buttonSaveFanSpeed}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row ac-disabled-feature">
|
||||
<ha-textfield
|
||||
.value=${this.currentAuxFanSpeed}
|
||||
.placeholder=${this.currentAuxFanSpeed}
|
||||
.label=${this._labelAuxFanSpeed}
|
||||
.type=${"number"}
|
||||
.min=${0}
|
||||
.max=${100}
|
||||
@input=${this._handleAuxFanSpeedChange}
|
||||
@keydown=${this._handleAuxFanSpeedKeyDown}
|
||||
></ha-textfield>
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSettings}
|
||||
@click=${this._handleSaveAuxFanSpeedButton}
|
||||
>
|
||||
${this._buttonSaveAuxFanSpeed}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
<div class="ac-settings-row ac-disabled-feature">
|
||||
<ha-textfield
|
||||
.value=${this.currentBoxFanSpeed}
|
||||
.placeholder=${this.currentBoxFanSpeed}
|
||||
.label=${this._labelBoxFanSpeed}
|
||||
.type=${"number"}
|
||||
.min=${0}
|
||||
.max=${100}
|
||||
@input=${this._handleBoxFanSpeedChange}
|
||||
@keydown=${this._handleBoxFanSpeedKeyDown}
|
||||
></ha-textfield>
|
||||
<ha-control-button
|
||||
.disabled=${this._changingSettings}
|
||||
@click=${this._handleSaveBoxFanSpeedButton}
|
||||
>
|
||||
${this._buttonSaveBoxFanSpeed}
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _setConfirmationMode = (
|
||||
ev: DomClickEvent<EvtTargConfirmationMode>,
|
||||
): void => {
|
||||
this._confirmationType = ev.currentTarget.confirmation_type;
|
||||
this._confirmMessage = localize(
|
||||
"card.print_settings.confirm_message",
|
||||
this.language,
|
||||
"action",
|
||||
localize(
|
||||
"common.actions." + (this._confirmationType as string),
|
||||
this.language,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
private _pressHassButton(suffix: string): void {
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService("button", "press", {
|
||||
entity_id: getPrinterEntityId(
|
||||
this.printerEntityIdPart,
|
||||
"button",
|
||||
suffix,
|
||||
),
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
}
|
||||
|
||||
private _handleConfirmApprove = (): void => {
|
||||
switch (this._confirmationType) {
|
||||
case AnycubicPrintOptionConfirmationType.PAUSE:
|
||||
this._pressHassButton("pause_print");
|
||||
break;
|
||||
case AnycubicPrintOptionConfirmationType.RESUME:
|
||||
this._pressHassButton("resume_print");
|
||||
break;
|
||||
case AnycubicPrintOptionConfirmationType.CANCEL:
|
||||
this._pressHassButton("cancel_print");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this._confirmationType = undefined;
|
||||
this._closeModal();
|
||||
};
|
||||
|
||||
private _handleConfirmCancel = (): void => {
|
||||
this._confirmationType = undefined;
|
||||
};
|
||||
|
||||
private _handleFanSpeedChange = (ev: Event): void => {
|
||||
const newSpeed = (
|
||||
ev.currentTarget as unknown as TextfieldChangeDetail<number>
|
||||
).value;
|
||||
this.currentFanSpeed = Number(newSpeed);
|
||||
this._userEditFanSpeed = true;
|
||||
};
|
||||
|
||||
private _handleAuxFanSpeedChange = (ev: Event): void => {
|
||||
const newSpeed = (
|
||||
ev.currentTarget as unknown as TextfieldChangeDetail<number>
|
||||
).value;
|
||||
this.currentAuxFanSpeed = Number(newSpeed);
|
||||
this._userEditAuxFanSpeed = true;
|
||||
};
|
||||
|
||||
private _handleBoxFanSpeedChange = (ev: Event): void => {
|
||||
const newSpeed = (
|
||||
ev.currentTarget as unknown as TextfieldChangeDetail<number>
|
||||
).value;
|
||||
this.currentBoxFanSpeed = Number(newSpeed);
|
||||
this._userEditBoxFanSpeed = true;
|
||||
};
|
||||
|
||||
private _handleFanSpeedKeyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.code === "Enter") {
|
||||
ev.preventDefault();
|
||||
this._submitChangedFanSpeed();
|
||||
} else {
|
||||
this._userEditFanSpeed = true;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleAuxFanSpeedKeyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.code === "Enter") {
|
||||
ev.preventDefault();
|
||||
this._submitChangedAuxFanSpeed();
|
||||
} else {
|
||||
this._userEditAuxFanSpeed = true;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleBoxFanSpeedKeyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.code === "Enter") {
|
||||
ev.preventDefault();
|
||||
this._submitChangedBoxFanSpeed();
|
||||
} else {
|
||||
this._userEditBoxFanSpeed = true;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleTargetTempNozzleChange = (ev: Event): void => {
|
||||
const newTemp = (
|
||||
ev.currentTarget as unknown as TextfieldChangeDetail<number>
|
||||
).value;
|
||||
this.currentTargetTempNozzle = Number(newTemp);
|
||||
this._userEditTargetTempNozzle = true;
|
||||
};
|
||||
|
||||
private _handleTargetTempHotbedChange = (ev: Event): void => {
|
||||
const newTemp = (
|
||||
ev.currentTarget as unknown as TextfieldChangeDetail<number>
|
||||
).value;
|
||||
this.currentTargetTempHotbed = Number(newTemp);
|
||||
this._userEditTargetTempHotbed = true;
|
||||
};
|
||||
|
||||
private _handleTargetTempNozzleKeyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.code === "Enter") {
|
||||
ev.preventDefault();
|
||||
this._submitChangedTargetTempNozzle();
|
||||
} else {
|
||||
this._userEditTargetTempNozzle = true;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleTargetTempHotbedKeyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.code === "Enter") {
|
||||
ev.preventDefault();
|
||||
this._submitChangedTargetTempHotbed();
|
||||
} else {
|
||||
this._userEditTargetTempHotbed = true;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleModalEvent = (evt: Event): void => {
|
||||
const e = evt as HASSDomEvent<ModalEventBase>;
|
||||
e.stopPropagation();
|
||||
if (e.detail.modalOpen) {
|
||||
this._isOpen = true;
|
||||
this._resetUserEdits();
|
||||
}
|
||||
};
|
||||
|
||||
private _handleDropdownEvent = (evt: Event): void => {
|
||||
const e = evt as HASSDomEvent<DropdownEvent<number, string>>;
|
||||
e.stopPropagation();
|
||||
this._userEditSpeedMode = true;
|
||||
if (typeof e.detail.key !== "undefined") {
|
||||
this.currentSpeedModeKey = e.detail.key;
|
||||
this.currentSpeedModeDescr =
|
||||
this.currentSpeedModeKey >= 0 &&
|
||||
this.currentSpeedModeKey in this.availableSpeedModes
|
||||
? this.availableSpeedModes[this.currentSpeedModeKey]
|
||||
: undefined;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleSaveFanSpeedButton = (): void => {
|
||||
this._submitChangedFanSpeed();
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _handleSaveAuxFanSpeedButton = (): void => {
|
||||
this._submitChangedAuxFanSpeed();
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _handleSaveBoxFanSpeedButton = (): void => {
|
||||
this._submitChangedBoxFanSpeed();
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _handleSaveSpeedModeButton = (): void => {
|
||||
this._submitChangedSpeedMode();
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _handleSaveTargetTempNozzleButton = (): void => {
|
||||
this._submitChangedTargetTempNozzle();
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _handleSaveTargetTempHotbedButton = (): void => {
|
||||
this._submitChangedTargetTempHotbed();
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _resetUserEdits(): void {
|
||||
this._userEditFanSpeed = false;
|
||||
this._userEditAuxFanSpeed = false;
|
||||
this._userEditBoxFanSpeed = false;
|
||||
this._userEditTargetTempNozzle = false;
|
||||
this._userEditTargetTempHotbed = false;
|
||||
this._userEditSpeedMode = false;
|
||||
}
|
||||
|
||||
private _closeModal = (e?: Event | undefined): void => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
this._isOpen = false;
|
||||
this._resetUserEdits();
|
||||
};
|
||||
|
||||
private _cardClick = (e: Event): void => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
private _serviceAvailable(serviceName: string): boolean {
|
||||
return Boolean(this.hass?.services?.[platform]?.[serviceName]);
|
||||
}
|
||||
|
||||
private _submitChangedSpeedMode(): void {
|
||||
if (this._userEditSpeedMode && this.selectedPrinterDevice) {
|
||||
const serv = "change_print_speed_mode";
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
return;
|
||||
}
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
speed_mode: this.currentSpeedModeKey,
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
private _submitChangedFanSpeed(): void {
|
||||
if (this._userEditFanSpeed && this.selectedPrinterDevice) {
|
||||
const serv = "change_print_fan_speed";
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
return;
|
||||
}
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
speed: this.currentFanSpeed,
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
private _submitChangedAuxFanSpeed(): void {
|
||||
if (this._userEditAuxFanSpeed && this.selectedPrinterDevice) {
|
||||
const serv = "change_print_aux_fan_speed";
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
return;
|
||||
}
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
speed: this.currentAuxFanSpeed,
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
private _submitChangedBoxFanSpeed(): void {
|
||||
if (this._userEditBoxFanSpeed && this.selectedPrinterDevice) {
|
||||
const serv = "change_print_box_fan_speed";
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
return;
|
||||
}
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
speed: this.currentBoxFanSpeed,
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
private _submitChangedTargetTempNozzle(): void {
|
||||
if (this._userEditTargetTempNozzle && this.selectedPrinterDevice) {
|
||||
const serv = "change_print_target_nozzle_temperature";
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
return;
|
||||
}
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
temperature: this.currentTargetTempNozzle,
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
private _submitChangedTargetTempHotbed(): void {
|
||||
if (this._userEditTargetTempHotbed && this.selectedPrinterDevice) {
|
||||
const serv = "change_print_target_hotbed_temperature";
|
||||
if (!this._serviceAvailable(serv)) {
|
||||
return;
|
||||
}
|
||||
this._changingSettings = true;
|
||||
this.hass
|
||||
.callService(platform, serv, {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
temperature: this.currentTargetTempHotbed,
|
||||
})
|
||||
.then(() => {
|
||||
this._changingSettings = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._changingSettings = false;
|
||||
});
|
||||
this._closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
${commonModalStyle}
|
||||
|
||||
.ac-settings-header {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ac-settings-row {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ac-disabled-feature {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
min-width: 150px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-control-button {
|
||||
min-width: 150px;
|
||||
margin: 8px 0px 0px 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ac-settings-buttonrow ha-control-button {
|
||||
min-width: 100%;
|
||||
margin: 8px 0px 0px 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ac-confirm-description {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ac-confirm-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ac-confirm-buttons ha-control-button {
|
||||
margin: 20px 30px 0px 30px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { CSSResult, LitElement, css, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
import { LitTemplateResult } from "../../../types";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-progress-line")
|
||||
export class AnycubicPrintercardProgressLine extends LitElement {
|
||||
@property({ type: String })
|
||||
public name: string;
|
||||
|
||||
@property({ type: Number })
|
||||
public value: string;
|
||||
|
||||
@property({ type: Number })
|
||||
public progress: number;
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const progressStyle = {
|
||||
width: String(this.progress) + "%",
|
||||
};
|
||||
return html`
|
||||
<div class="ac-stat-line">
|
||||
<p class="ac-stat-heading">${this.name}</p>
|
||||
<div class="ac-stat-value">
|
||||
<div class="ac-progress-bar">
|
||||
<div class="ac-stat-text">${this.value}</div>
|
||||
<div
|
||||
class="ac-progress-line"
|
||||
style=${styleMap(progressStyle)}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-stat-line {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.ac-stat-value {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
max-width: calc(100% - 120px);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ac-stat-text {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ac-stat-heading {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ac-progress-bar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #8b8b8b6e;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ac-progress-line {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: #ee8f36e6;
|
||||
border-right: 2px solid #ffd151e6;
|
||||
box-shadow: 4px 0px 6px 0px rgb(255 245 126 / 25%);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { CSSResult, LitElement, css, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
import { LitTemplateResult } from "../../../types";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-stat-line")
|
||||
export class AnycubicPrintercardStatLine extends LitElement {
|
||||
@property({ type: String })
|
||||
public name: string;
|
||||
|
||||
@property({ type: String })
|
||||
public value: string;
|
||||
|
||||
@property({ type: String })
|
||||
public unit?: string = "";
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="ac-stat-line">
|
||||
<p class="ac-stat-text ac-stat-heading">${this.name}</p>
|
||||
<p class="ac-stat-text">${this.value}${this.unit}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-stat-line {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.ac-stat-text {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
max-width: calc(100% - 120px);
|
||||
text-align: right;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.ac-stat-heading {
|
||||
font-weight: bold;
|
||||
max-width: unset;
|
||||
overflow: unset;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
|
||||
import { localize } from "../../../../localize/localize";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import {
|
||||
getPrinterBinarySensorState,
|
||||
getPrinterSensorStateObj,
|
||||
speedModesFromStateObj,
|
||||
toTitleCase,
|
||||
} from "../../../helpers";
|
||||
import {
|
||||
AnycubicSpeedModeEntity,
|
||||
HassEntity,
|
||||
HassEntityInfos,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
PrinterCardStatType,
|
||||
TemperatureUnit,
|
||||
TranslationDict,
|
||||
} from "../../../types";
|
||||
|
||||
import "./progress_line.ts";
|
||||
import "./stat_line.ts";
|
||||
import "./temperature_stat.ts";
|
||||
import "./time_stat.ts";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-stats-component")
|
||||
export class AnycubicPrintercardStatsComponent extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ attribute: "monitored-stats" })
|
||||
public monitoredStats: PrinterCardStatType[];
|
||||
|
||||
@property({ attribute: "show-percent", type: Boolean })
|
||||
public showPercent?: boolean;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public round?: boolean = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public use_24hr?: boolean;
|
||||
|
||||
@property({ attribute: "temperature-unit", type: String })
|
||||
public temperatureUnit: TemperatureUnit = TemperatureUnit.C;
|
||||
|
||||
@property({ attribute: "printer-entities" })
|
||||
public printerEntities: HassEntityInfos;
|
||||
|
||||
@property({ attribute: "printer-entity-id-part" })
|
||||
public printerEntityIdPart: string | undefined;
|
||||
|
||||
@property({ attribute: "progress-percent" })
|
||||
public progressPercent: number = 0;
|
||||
|
||||
@state()
|
||||
private _statTranslations: TranslationDict;
|
||||
|
||||
@state()
|
||||
private _entETA: HassEntity;
|
||||
|
||||
@state()
|
||||
private _entElapsed: HassEntity;
|
||||
|
||||
@state()
|
||||
private _entRemaining: HassEntity;
|
||||
|
||||
@state()
|
||||
private _entBedCurrent: HassEntity;
|
||||
|
||||
@state()
|
||||
private _entHotendCurrent: HassEntity;
|
||||
|
||||
@state()
|
||||
private _entBedTarget: HassEntity;
|
||||
|
||||
@state()
|
||||
private _entHotendTarget: HassEntity;
|
||||
|
||||
@state()
|
||||
private _valStatus: string;
|
||||
|
||||
@state()
|
||||
private _valOnline: string;
|
||||
|
||||
@state()
|
||||
private _valAvailability: string;
|
||||
|
||||
@state()
|
||||
private _valJobName: string;
|
||||
|
||||
@state()
|
||||
private _valCurrentLayer: string;
|
||||
|
||||
@state()
|
||||
private _valSpeedMode: string;
|
||||
|
||||
@state()
|
||||
private _valFanSpeed: string;
|
||||
|
||||
@state()
|
||||
private _valDryStatus: string;
|
||||
|
||||
@state()
|
||||
private _valDryRemain: string;
|
||||
|
||||
@state()
|
||||
private _valDryProgress: number = 0;
|
||||
|
||||
@state()
|
||||
private _valOnTime: string;
|
||||
|
||||
@state()
|
||||
private _valOffTime: string;
|
||||
|
||||
@state()
|
||||
private _valBottomTime: string;
|
||||
|
||||
@state()
|
||||
private _valModelHeight: string;
|
||||
|
||||
@state()
|
||||
private _valBottomLayers: string;
|
||||
|
||||
@state()
|
||||
private _valZUpHeight: string;
|
||||
|
||||
@state()
|
||||
private _valZUpSpeed: string;
|
||||
|
||||
@state()
|
||||
private _valZDownSpeed: string;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("printerEntities") ||
|
||||
changedProperties.has("printerEntityIdPart")
|
||||
) {
|
||||
this._entETA = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_time_remaining",
|
||||
);
|
||||
this._entElapsed = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_time_elapsed",
|
||||
);
|
||||
this._entRemaining = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_time_remaining",
|
||||
);
|
||||
this._entBedCurrent = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"hotbed_temperature",
|
||||
);
|
||||
this._entHotendCurrent = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"nozzle_temperature",
|
||||
);
|
||||
this._entBedTarget = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"target_hotbed_temperature",
|
||||
);
|
||||
this._entHotendTarget = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"target_nozzle_temperature",
|
||||
);
|
||||
this._valStatus = toTitleCase(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_state",
|
||||
).state,
|
||||
);
|
||||
this._valOnline = getPrinterBinarySensorState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"printer_online",
|
||||
"Online",
|
||||
"Offline",
|
||||
"unknown",
|
||||
) as string;
|
||||
this._valAvailability = toTitleCase(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"current_status",
|
||||
).state,
|
||||
);
|
||||
this._valJobName = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_name",
|
||||
).state;
|
||||
this._valCurrentLayer = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_current_layer",
|
||||
).state;
|
||||
const speedModeState: AnycubicSpeedModeEntity = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_speed_mode",
|
||||
"",
|
||||
{ available_modes: [], print_speed_mode_code: -1 },
|
||||
) as AnycubicSpeedModeEntity;
|
||||
const availableSpeedModes = speedModesFromStateObj(speedModeState);
|
||||
const currentSpeedModeKey: number =
|
||||
(speedModeState.attributes.print_speed_mode_code as
|
||||
| number
|
||||
| undefined) ?? 0;
|
||||
this._valSpeedMode =
|
||||
currentSpeedModeKey >= 0 && currentSpeedModeKey in availableSpeedModes
|
||||
? availableSpeedModes[currentSpeedModeKey]
|
||||
: "Unknown";
|
||||
this._valFanSpeed = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"fan_speed",
|
||||
0,
|
||||
).state;
|
||||
this._valDryStatus = getPrinterBinarySensorState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"drying_active",
|
||||
"Drying",
|
||||
"Not Drying",
|
||||
"unknown",
|
||||
) as string;
|
||||
const dryTotal = Number(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"drying_total_duration",
|
||||
0,
|
||||
).state,
|
||||
);
|
||||
const dryRemain = Number(
|
||||
getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"drying_remaining_time",
|
||||
0,
|
||||
).state,
|
||||
);
|
||||
this._valDryRemain = !isNaN(dryRemain) ? `${dryRemain} Mins` : "";
|
||||
this._valDryProgress =
|
||||
!isNaN(dryTotal) && dryTotal > 0 ? (dryRemain / dryTotal) * 100 : 0;
|
||||
this._valOnTime = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_on_time",
|
||||
0,
|
||||
).state;
|
||||
this._valOffTime = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_off_time",
|
||||
0,
|
||||
).state;
|
||||
this._valBottomTime = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_bottom_time",
|
||||
0,
|
||||
).state;
|
||||
this._valModelHeight = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_model_height",
|
||||
0,
|
||||
).state;
|
||||
this._valBottomLayers = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_bottom_layers",
|
||||
0,
|
||||
).state;
|
||||
this._valZUpHeight = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_z_up_height",
|
||||
0,
|
||||
).state;
|
||||
this._valZUpSpeed = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_z_up_speed",
|
||||
0,
|
||||
).state;
|
||||
this._valZDownSpeed = getPrinterSensorStateObj(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_z_down_speed",
|
||||
0,
|
||||
).state;
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("language") ||
|
||||
changedProperties.has("monitoredStats")
|
||||
) {
|
||||
this._statTranslations = this.monitoredStats.reduce((fConf, statKey) => {
|
||||
fConf[statKey] = localize(
|
||||
`card.monitored_stats.${statKey}`,
|
||||
this.language,
|
||||
);
|
||||
return fConf;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="ac-stats-box ac-stats-section">
|
||||
${this.showPercent
|
||||
? html`
|
||||
<div class="ac-stats-box ac-stats-part-percent">
|
||||
<p class="ac-stats-part-percent-text">
|
||||
${this.round
|
||||
? Math.round(this.progressPercent)
|
||||
: this.progressPercent}%
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
<div class="ac-stats-box ac-stats-section">${this._renderStats()}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderStats(): LitTemplateResult {
|
||||
return repeat(
|
||||
this.monitoredStats,
|
||||
(condition) => condition,
|
||||
(condition, _index): LitTemplateResult => {
|
||||
switch (condition) {
|
||||
case PrinterCardStatType.Status:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valStatus}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
case PrinterCardStatType.ETA:
|
||||
return html`
|
||||
<anycubic-printercard-stat-time
|
||||
.timeEntity=${this._entETA}
|
||||
.timeType=${condition}
|
||||
.name=${this._statTranslations[condition]}
|
||||
.direction=${0}
|
||||
.round=${this.round}
|
||||
.use_24hr=${this.use_24hr}
|
||||
></anycubic-printercard-stat-time>
|
||||
`;
|
||||
case PrinterCardStatType.Elapsed:
|
||||
return html`
|
||||
<anycubic-printercard-stat-time
|
||||
.timeEntity=${this._entElapsed}
|
||||
.timeType=${condition}
|
||||
.name=${this._statTranslations[condition]}
|
||||
.direction=${1}
|
||||
.round=${this.round}
|
||||
.use_24hr=${this.use_24hr}
|
||||
></anycubic-printercard-stat-time>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.Remaining:
|
||||
return html`
|
||||
<anycubic-printercard-stat-time
|
||||
.timeEntity=${this._entRemaining}
|
||||
.timeType=${condition}
|
||||
.name=${this._statTranslations[condition]}
|
||||
.direction=${-1}
|
||||
.round=${this.round}
|
||||
.use_24hr=${this.use_24hr}
|
||||
></anycubic-printercard-stat-time>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.BedCurrent:
|
||||
return html`
|
||||
<anycubic-printercard-stat-temperature
|
||||
.name=${this._statTranslations[condition]}
|
||||
.temperatureEntity=${this._entBedCurrent}
|
||||
.round=${this.round}
|
||||
.temperatureUnit=${this.temperatureUnit}
|
||||
></anycubic-printercard-stat-temperature>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.HotendCurrent:
|
||||
return html`
|
||||
<anycubic-printercard-stat-temperature
|
||||
.name=${this._statTranslations[condition]}
|
||||
.temperatureEntity=${this._entHotendCurrent}
|
||||
.round=${this.round}
|
||||
.temperatureUnit=${this.temperatureUnit}
|
||||
></anycubic-printercard-stat-temperature>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.BedTarget:
|
||||
return html`
|
||||
<anycubic-printercard-stat-temperature
|
||||
.name=${this._statTranslations[condition]}
|
||||
.temperatureEntity=${this._entBedTarget}
|
||||
.round=${this.round}
|
||||
.temperatureUnit=${this.temperatureUnit}
|
||||
></anycubic-printercard-stat-temperature>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.HotendTarget:
|
||||
return html`
|
||||
<anycubic-printercard-stat-temperature
|
||||
.name=${this._statTranslations[condition]}
|
||||
.temperatureEntity=${this._entHotendTarget}
|
||||
.round=${this.round}
|
||||
.temperatureUnit=${this.temperatureUnit}
|
||||
></anycubic-printercard-stat-temperature>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.PrinterOnline:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valOnline}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.Availability:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valAvailability}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.ProjectName:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valJobName}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.CurrentLayer:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valCurrentLayer}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.SpeedMode:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valSpeedMode}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.FanSpeed:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valFanSpeed}
|
||||
.unit=${"%"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.DryingStatus:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valDryStatus}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.DryingTime:
|
||||
return html`
|
||||
<anycubic-printercard-progress-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valDryRemain}
|
||||
.progress=${this._valDryProgress}
|
||||
></anycubic-printercard-progress-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.OnTime:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valOnTime}
|
||||
.unit=${"s"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.OffTime:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valOffTime}
|
||||
.unit=${"s"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.BottomTime:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valBottomTime}
|
||||
.unit=${"s"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.ModelHeight:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valModelHeight}
|
||||
.unit=${"mm"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.BottomLayers:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valBottomLayers}
|
||||
.unit=${"layers"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.ZUpHeight:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valZUpHeight}
|
||||
.unit=${"mm"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.ZUpSpeed:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valZUpSpeed}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
case PrinterCardStatType.ZDownSpeed:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${this._statTranslations[condition]}
|
||||
.value=${this._valZDownSpeed}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
|
||||
default:
|
||||
return html`
|
||||
<anycubic-printercard-stat-line
|
||||
.name=${"Unknown"}
|
||||
.value=${"<unknown>"}
|
||||
></anycubic-printercard-stat-line>
|
||||
`;
|
||||
}
|
||||
},
|
||||
) as LitTemplateResult;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-stats-box {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ac-stats-section {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ac-stats-part-percent {
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.ac-stats-part-percent-text {
|
||||
margin: 0px;
|
||||
font-size: 42px;
|
||||
font-weight: bold;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { CSSResult, LitElement, css, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import { getEntityTemperature } from "../../../helpers";
|
||||
import { HassEntity, LitTemplateResult, TemperatureUnit } from "../../../types";
|
||||
|
||||
import "./stat_line.ts";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-stat-temperature")
|
||||
export class AnycubicPrintercardStatTemperature extends LitElement {
|
||||
@property({ type: String })
|
||||
public name: string;
|
||||
|
||||
@property({ attribute: "temperature-entity" })
|
||||
public temperatureEntity: HassEntity;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public round?: boolean;
|
||||
|
||||
@property({ attribute: "temperature-unit", type: String })
|
||||
public temperatureUnit: TemperatureUnit;
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`<anycubic-printercard-stat-line
|
||||
.name=${this.name}
|
||||
.value=${getEntityTemperature(
|
||||
this.temperatureEntity,
|
||||
this.temperatureUnit,
|
||||
this.round,
|
||||
)}
|
||||
></anycubic-printercard-stat-line>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
import { customElementIfUndef } from "../../../internal/register-custom-element";
|
||||
|
||||
import { calculateTimeStat, getEntityTotalSeconds } from "../../../helpers";
|
||||
import {
|
||||
CalculatedTimeType,
|
||||
HassEntity,
|
||||
LitTemplateResult,
|
||||
} from "../../../types";
|
||||
|
||||
import "./stat_line.ts";
|
||||
|
||||
@customElementIfUndef("anycubic-printercard-stat-time")
|
||||
export class AnycubicPrintercardStatTime extends LitElement {
|
||||
@property({ attribute: "time-entity" })
|
||||
public timeEntity: HassEntity;
|
||||
|
||||
@property({ attribute: "time-type" })
|
||||
public timeType: CalculatedTimeType;
|
||||
|
||||
@property({ type: String })
|
||||
public name: string;
|
||||
|
||||
@property({ type: Number })
|
||||
public direction: number;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public round?: boolean;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public use_24hr?: boolean;
|
||||
|
||||
@property({ attribute: "is-seconds", type: Boolean })
|
||||
public isSeconds?: boolean;
|
||||
|
||||
@state()
|
||||
private currentTime: number | string | undefined = 0;
|
||||
|
||||
@state()
|
||||
private lastIntervalId: number = -1;
|
||||
|
||||
protected override willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (!changedProperties.has("timeEntity")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastIntervalId !== -1) {
|
||||
clearInterval(this.lastIntervalId);
|
||||
}
|
||||
|
||||
this.currentTime = getEntityTotalSeconds(this.timeEntity);
|
||||
|
||||
this.lastIntervalId = setInterval(() => {
|
||||
this._incTime();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.lastIntervalId === -1) {
|
||||
this.lastIntervalId = setInterval(() => {
|
||||
this._incTime();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
if (this.lastIntervalId !== -1) {
|
||||
clearInterval(this.lastIntervalId);
|
||||
this.lastIntervalId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`<anycubic-printercard-stat-line
|
||||
.name=${this.name}
|
||||
.value=${calculateTimeStat(
|
||||
this.currentTime,
|
||||
this.timeType,
|
||||
this.round,
|
||||
this.use_24hr,
|
||||
)}
|
||||
></anycubic-printercard-stat-line>`;
|
||||
}
|
||||
|
||||
private _incTime(): void {
|
||||
if (
|
||||
this.currentTime === 0 ||
|
||||
(this.currentTime && !isNaN(this.currentTime as number))
|
||||
) {
|
||||
this.currentTime = Number(this.currentTime) + this.direction;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { CSSResult, css } from "lit";
|
||||
|
||||
export const commonModalStyle: CSSResult = css`
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.ac-modal-container {
|
||||
border-radius: 16px;
|
||||
background-color: var(--primary-background-color);
|
||||
margin: auto;
|
||||
padding: 50px;
|
||||
width: 80%;
|
||||
min-height: 150px;
|
||||
max-width: 600px;
|
||||
margin-top: 50px;
|
||||
box-shadow: 0px 0px 15px 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.ac-modal-card {
|
||||
padding: 20px;
|
||||
}
|
||||
.ac-modal-close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ac-modal-close:hover,
|
||||
.ac-modal-close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ac-modal-label {
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
.ac-modal-container {
|
||||
width: 95%;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,284 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { mdiCheck, mdiChevronDown, mdiChevronUp } from "@mdi/js";
|
||||
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
import { customElementIfUndef } from "../../internal/register-custom-element";
|
||||
import {
|
||||
DomClickEvent,
|
||||
EvtTargDirection,
|
||||
LitTemplateResult,
|
||||
} from "../../types";
|
||||
|
||||
@customElementIfUndef("anycubic-ui-multi-select-reorder-item")
|
||||
export class AnycubicUIMultiSelectReorderItem extends LitElement {
|
||||
@property()
|
||||
public item: any;
|
||||
|
||||
@property({ attribute: "selected-items" })
|
||||
public selectedItems: any[];
|
||||
|
||||
@property({ attribute: "unused-items" })
|
||||
public unusedItems: any[];
|
||||
|
||||
@property()
|
||||
public reorder: (item: any, mod: number) => void;
|
||||
|
||||
@property()
|
||||
public toggle: (item: any) => void;
|
||||
|
||||
@state()
|
||||
private _isActive: boolean;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
changedProperties.has("selectedItems") ||
|
||||
changedProperties.has("item")
|
||||
) {
|
||||
this._isActive = this.selectedItems.includes(this.item);
|
||||
}
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
if (
|
||||
changedProperties.has("_isActive") ||
|
||||
changedProperties.has("selectedItems") ||
|
||||
changedProperties.has("unusedItems") ||
|
||||
changedProperties.has("item")
|
||||
) {
|
||||
this.style.top =
|
||||
String(
|
||||
this._isActive
|
||||
? 56 * this.selectedItems.indexOf(this.item)
|
||||
: 56 *
|
||||
(this.selectedItems.length +
|
||||
this.unusedItems.indexOf(this.item)),
|
||||
) + "px";
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const classesItemText = {
|
||||
"ac-ui-deselected": !this._isActive,
|
||||
};
|
||||
return html`
|
||||
<button class="ac-ui-msr-select" @click=${this._toggle_item}>
|
||||
${this._isActive
|
||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||
: nothing}
|
||||
</button>
|
||||
<p class="ac-ui-msr-itemtext ${classMap(classesItemText)}">
|
||||
${this.item}
|
||||
</p>
|
||||
<div>
|
||||
<button
|
||||
class="ac-ui-msr-position"
|
||||
.direction=${1}
|
||||
@click=${this._reorder_item}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChevronDown}></ha-svg-icon>
|
||||
</button>
|
||||
<button
|
||||
class="ac-ui-msr-position"
|
||||
.direction=${-1}
|
||||
@click=${this._reorder_item}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChevronUp}></ha-svg-icon>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggle_item = (): void => {
|
||||
this.toggle(this.item);
|
||||
};
|
||||
|
||||
private _reorder_item = (ev: DomClickEvent<EvtTargDirection>): void => {
|
||||
if (this._isActive) {
|
||||
this.reorder(this.item, ev.currentTarget.direction);
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ac-ui-msr-itemtext {
|
||||
flex-grow: 1;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.ac-ui-msr-select {
|
||||
box-sizing: border-box;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
outline: none;
|
||||
border: none;
|
||||
margin-right: 16px;
|
||||
padding: 0px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--primary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ac-ui-msr-position {
|
||||
box-sizing: border-box;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 8px;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin-left: 16px;
|
||||
color: var(--primary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElementIfUndef("anycubic-ui-multi-select-reorder")
|
||||
export class AnycubicUIMultiSelectReorder extends LitElement {
|
||||
@property({ attribute: "available-options" })
|
||||
public availableOptions: object;
|
||||
|
||||
@property({ attribute: "initial-items" })
|
||||
public initialItems: (string | number)[];
|
||||
|
||||
@property({ attribute: "on-change" })
|
||||
public onChange: (sel: (string | number)[]) => void;
|
||||
|
||||
@state()
|
||||
private _allOptions: (string | number)[];
|
||||
|
||||
@state()
|
||||
private _selectedItems: (string | number)[];
|
||||
|
||||
@state()
|
||||
private _unusedItems: (string | number)[];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this._allOptions = Object.values(this.availableOptions) as (
|
||||
| string
|
||||
| number
|
||||
)[];
|
||||
this._setSelectedItems(
|
||||
[...this.initialItems].filter((item: string | number) =>
|
||||
this._allOptions.includes(item),
|
||||
),
|
||||
);
|
||||
this._unusedItems = this._allOptions.filter(
|
||||
(item) => !this.initialItems.includes(item),
|
||||
);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesCont = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
height: this._allOptions
|
||||
? String(this._allOptions.length * 56) + "px"
|
||||
: "0px",
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
return this._allOptions
|
||||
? html`
|
||||
<div style=${styleMap(stylesCont)}>
|
||||
${map(this._allOptions, (item, _index) => {
|
||||
return html`
|
||||
<anycubic-ui-multi-select-reorder-item
|
||||
.item=${item}
|
||||
.selectedItems=${this._selectedItems}
|
||||
.unusedItems=${this._unusedItems}
|
||||
.reorder=${this._reorder}
|
||||
.toggle=${this._toggle}
|
||||
></anycubic-ui-multi-select-reorder-item>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _setSelectedItems(selectedItems: (string | number)[]): void {
|
||||
this._selectedItems = selectedItems;
|
||||
this.onChange(this._selectedItems);
|
||||
}
|
||||
|
||||
private _reorder = (item: string | number, mod: number): void => {
|
||||
const ind = this._selectedItems.indexOf(item);
|
||||
const newPos = ind + mod;
|
||||
|
||||
if (newPos < 0 || newPos > this._selectedItems.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clone = this._selectedItems.slice(0);
|
||||
const tmp = clone[newPos];
|
||||
clone[newPos] = item;
|
||||
clone[ind] = tmp;
|
||||
|
||||
this._setSelectedItems(clone);
|
||||
};
|
||||
|
||||
private _toggle = (item: string | number): void => {
|
||||
if (this._selectedItems.includes(item)) {
|
||||
const i = this._selectedItems.indexOf(item);
|
||||
|
||||
this._setSelectedItems([
|
||||
...this._selectedItems.slice(0, i),
|
||||
...this._selectedItems.slice(i + 1),
|
||||
]);
|
||||
|
||||
this._unusedItems = [item, ...this._unusedItems];
|
||||
} else {
|
||||
const i = this._unusedItems.indexOf(item);
|
||||
|
||||
this._unusedItems = [
|
||||
...this._unusedItems.slice(0, i),
|
||||
...this._unusedItems.slice(i + 1),
|
||||
];
|
||||
|
||||
this._setSelectedItems([...this._selectedItems, item]);
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import { mdiChevronDown } from "@mdi/js";
|
||||
|
||||
import { CSSResult, LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
import { customElementIfUndef } from "../../internal/register-custom-element";
|
||||
|
||||
import { fireEvent } from "../../fire_event";
|
||||
import { DomClickEvent, EvtTargItemKey, LitTemplateResult } from "../../types";
|
||||
|
||||
@customElementIfUndef("anycubic-ui-select-dropdown-item")
|
||||
export class AnycubicUISelectDropdownItem extends LitElement {
|
||||
@property()
|
||||
public item: string;
|
||||
|
||||
@state()
|
||||
private _isActive: boolean = false;
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesOption = {
|
||||
filter: this._isActive ? "brightness(80%)" : "brightness(100%)",
|
||||
};
|
||||
return html`
|
||||
<button
|
||||
class="ac-ui-seld-select"
|
||||
style=${styleMap(stylesOption)}
|
||||
@mouseenter=${this._setActive}
|
||||
@mousedown=${this._setActive}
|
||||
@mouseup=${this._setInactive}
|
||||
@mouseleave=${this._setInactive}
|
||||
>
|
||||
${this.item}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _setActive = (): void => {
|
||||
this._isActive = true;
|
||||
};
|
||||
|
||||
private _setInactive = (): void => {
|
||||
this._isActive = false;
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ac-ui-seld-select {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 48px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElementIfUndef("anycubic-ui-select-dropdown")
|
||||
export class AnycubicUISelectDropdown extends LitElement {
|
||||
@property({ attribute: "available-options" })
|
||||
public availableOptions?: object;
|
||||
|
||||
@property()
|
||||
public placeholder: string;
|
||||
|
||||
@property({ attribute: "initial-item" })
|
||||
public initialItem: string | undefined;
|
||||
|
||||
@state()
|
||||
private _selectedItem: string | undefined;
|
||||
|
||||
@state()
|
||||
private _active: boolean = false;
|
||||
|
||||
@state()
|
||||
private _hidden: boolean = false;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async firstUpdated(): Promise<void> {
|
||||
this._selectedItem = this.initialItem;
|
||||
this._hidden = true;
|
||||
this._active = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
const stylesButton = {
|
||||
backgroundColor: this._active ? "rgba(0,0,0,0.3)" : "rgba(0,0,0,0.15)",
|
||||
};
|
||||
const stylesOptions = {
|
||||
opacity: this._hidden ? 0.0 : 1.0,
|
||||
transform: this._hidden ? "scaleY(0.0)" : "scaleY(1.0)",
|
||||
};
|
||||
return this.availableOptions
|
||||
? html`
|
||||
<button
|
||||
class="ac-ui-select-button"
|
||||
style=${styleMap(stylesButton)}
|
||||
@click=${this._showOptions}
|
||||
@mouseenter=${this._setActive}
|
||||
@mouseleave=${this._setInactive}
|
||||
>
|
||||
${this._selectedItem ? this._selectedItem : this.placeholder}
|
||||
<ha-svg-icon .path=${mdiChevronDown}></ha-svg-icon>
|
||||
</button>
|
||||
<div class="ac-ui-select-options" style=${styleMap(stylesOptions)}>
|
||||
${this._renderOptions()}
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private _renderOptions(): Generator<unknown, void, LitTemplateResult> {
|
||||
return map(
|
||||
Object.keys(this.availableOptions as object),
|
||||
(key: string | number, _index: number): LitTemplateResult => {
|
||||
return html`
|
||||
<anycubic-ui-select-dropdown-item
|
||||
.item=${(this.availableOptions as object)[key]}
|
||||
.item_key=${key}
|
||||
@click=${this._selectItem}
|
||||
></anycubic-ui-select-dropdown-item>
|
||||
`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private _showOptions = (): void => {
|
||||
this._hidden = false;
|
||||
};
|
||||
|
||||
private _hideOptions = (): void => {
|
||||
this._hidden = true;
|
||||
};
|
||||
|
||||
private _setActive = (): void => {
|
||||
this._active = true;
|
||||
};
|
||||
|
||||
private _setInactive = (): void => {
|
||||
this._active = false;
|
||||
};
|
||||
|
||||
private _selectItem = (ev: DomClickEvent<EvtTargItemKey>): void => {
|
||||
if (!this.availableOptions) {
|
||||
return;
|
||||
}
|
||||
const key = ev.currentTarget.item_key;
|
||||
this._selectedItem = this.availableOptions[key] as string | undefined;
|
||||
fireEvent(this, "ac-select-dropdown", {
|
||||
key: key,
|
||||
value: this.availableOptions[key] as string | undefined,
|
||||
});
|
||||
this._hidden = true;
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ac-ui-select-button {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 48px;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
align-items: center;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.ac-ui-select-options {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0px 10px 20px rgba(0, 0, 0, 0.19),
|
||||
0px 6px 6px rgba(0, 0, 0, 0.23);
|
||||
z-index: 11;
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
transform-origin: top center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
7
custom_components/kobrax_lan/frontend_panel/src/const.ts
Normal file
7
custom_components/kobrax_lan/frontend_panel/src/const.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const platform = "kobrax_lan";
|
||||
|
||||
export const DEBUG = false;
|
||||
|
||||
export const LIGHT_ENTITY_DOMAINS = ["light"];
|
||||
export const SWITCH_ENTITY_DOMAINS = ["switch"];
|
||||
export const CAMERA_ENTITY_DOMAINS = ["camera"];
|
||||
@@ -0,0 +1,65 @@
|
||||
// Polymer legacy event helpers used courtesy of the Polymer project.
|
||||
//
|
||||
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/* global HASSDomEvents */
|
||||
|
||||
declare global {
|
||||
// tslint:disable-next-line
|
||||
interface HASSDomEvents {}
|
||||
}
|
||||
|
||||
export type ValidHassDomEvent = keyof HASSDomEvents;
|
||||
|
||||
export interface HASSDomEvent<T> extends Event {
|
||||
detail: T;
|
||||
}
|
||||
|
||||
export const fireEvent = (
|
||||
node: HTMLElement | Window,
|
||||
type: string,
|
||||
evt_detail?: object | null,
|
||||
evt_options?: {
|
||||
bubbles?: boolean;
|
||||
cancelable?: boolean;
|
||||
composed?: boolean;
|
||||
},
|
||||
): Event => {
|
||||
const options = evt_options || {};
|
||||
const detail =
|
||||
evt_detail === null || evt_detail === undefined ? {} : evt_detail;
|
||||
const event = new Event(type, {
|
||||
bubbles: options.bubbles === undefined ? true : options.bubbles,
|
||||
cancelable: Boolean(options.cancelable),
|
||||
composed: options.composed === undefined ? true : options.composed,
|
||||
});
|
||||
(event as HASSDomEvent<typeof detail>).detail = detail;
|
||||
node.dispatchEvent(event);
|
||||
return event;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
enum HapticStrength {
|
||||
Light = "light",
|
||||
Medium = "medium",
|
||||
Heavy = "heavy",
|
||||
}
|
||||
|
||||
interface HapticEvent extends Event {
|
||||
detail: HapticStrength;
|
||||
}
|
||||
|
||||
const fireHaptic = (
|
||||
hapticStrength: HapticStrength = HapticStrength.Medium,
|
||||
): void => {
|
||||
const event: HapticEvent = new Event("haptic") as HapticEvent;
|
||||
event.detail = hapticStrength;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (window) {
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
export { fireHaptic, HapticStrength };
|
||||
840
custom_components/kobrax_lan/frontend_panel/src/helpers.ts
Normal file
840
custom_components/kobrax_lan/frontend_panel/src/helpers.ts
Normal file
@@ -0,0 +1,840 @@
|
||||
import { utc as dfnsUtc } from "@date-fns/utc";
|
||||
import {
|
||||
Duration as dfnsDuration,
|
||||
format as dfnsFormat,
|
||||
intervalToDuration as dfnsIntervalToDuration,
|
||||
} from "date-fns";
|
||||
|
||||
import { fireEvent } from "./fire_event";
|
||||
import {
|
||||
AnycubicCardConfig,
|
||||
AnycubicLitNode,
|
||||
AnycubicMaterialType,
|
||||
AnycubicSpeedMode,
|
||||
AnycubicSpeedModeEntity,
|
||||
AnycubicSpeedModes,
|
||||
CalculatedTimeType,
|
||||
HassDevice,
|
||||
HassDeviceList,
|
||||
HassEmptyEntity,
|
||||
HassEntity,
|
||||
HassEntityInfo,
|
||||
HassEntityInfos,
|
||||
HassRoute,
|
||||
HomeAssistant,
|
||||
PrinterCardStatType,
|
||||
TemperatureUnit,
|
||||
} from "./types";
|
||||
|
||||
const stylePxKeys = ["width", "height", "left", "top"];
|
||||
|
||||
export function updateElementStyleWithObject(
|
||||
el: HTMLElement | undefined,
|
||||
updateObj: any, // eslint-disable-line
|
||||
): void {
|
||||
Object.keys(updateObj as object).forEach((key) => {
|
||||
// eslint-disable-next-line
|
||||
if (stylePxKeys.includes(key) && !isNaN(updateObj[key])) {
|
||||
// eslint-disable-next-line
|
||||
updateObj[key] = (updateObj[key].toString()) + "px";
|
||||
}
|
||||
});
|
||||
if (el) {
|
||||
Object.assign(el.style, updateObj);
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyEntity(entityParams: HassEmptyEntity): HassEntity {
|
||||
return {
|
||||
state: entityParams.state,
|
||||
attributes: entityParams.attributes,
|
||||
entity_id: "invalid_domain.invalid_entity",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: {
|
||||
id: "",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function numberFromString(str: string): number {
|
||||
const matches = str.match(/\d+/);
|
||||
return Number(matches ? matches[0] : -1);
|
||||
}
|
||||
|
||||
export function toTitleCase(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.split(" ")
|
||||
.map((word: string) => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function buildImageUrlFromEntity(entityState: HassEntity): string {
|
||||
const token: string = entityState.attributes.access_token as string;
|
||||
return `${window.location.origin}/api/image_proxy/${entityState.entity_id}?token=${token}`;
|
||||
}
|
||||
|
||||
export function buildCameraUrlFromEntity(entityState: HassEntity): string {
|
||||
const token: string = entityState.attributes.access_token as string;
|
||||
return `${window.location.origin}/api/camera_proxy_stream/${entityState.entity_id}?token=${token}`;
|
||||
}
|
||||
|
||||
export function prettyFilename(str: string): string {
|
||||
const splitI = str.indexOf("-0.");
|
||||
const splitName =
|
||||
splitI > 0 ? [str.slice(0, splitI), str.slice(splitI + 1)] : [str];
|
||||
const chunksFirst = splitName[0].match(/.{1,10}/g);
|
||||
const joinFirst = chunksFirst ? chunksFirst.join("\n") : splitName[0];
|
||||
return splitName.length > 1
|
||||
? joinFirst + "-" + splitName.slice(1)[0]
|
||||
: joinFirst;
|
||||
}
|
||||
|
||||
export function getEntityState(
|
||||
hass: HomeAssistant,
|
||||
entityInfo: HassEntityInfo | undefined,
|
||||
): HassEntity | undefined {
|
||||
return entityInfo ? hass.states[entityInfo.entity_id] : undefined;
|
||||
}
|
||||
|
||||
export function getEntityStateFloat(
|
||||
hass: HomeAssistant,
|
||||
entityInfo: HassEntityInfo | undefined,
|
||||
): number {
|
||||
const entityState = getEntityState(hass, entityInfo);
|
||||
const stateFloat = entityState ? parseFloat(entityState.state) : 0;
|
||||
return !isNaN(stateFloat) ? stateFloat : 0;
|
||||
}
|
||||
|
||||
export function getEntityStateString(
|
||||
hass: HomeAssistant,
|
||||
entityInfo: HassEntityInfo | undefined,
|
||||
): string {
|
||||
const entityState = getEntityState(hass, entityInfo);
|
||||
return entityState ? String(entityState.state) : "";
|
||||
}
|
||||
|
||||
export function getEntityStateBinary(
|
||||
hass: HomeAssistant,
|
||||
entityInfo: HassEntityInfo | undefined,
|
||||
onValue: string | boolean,
|
||||
offValue: string | boolean,
|
||||
): string | boolean {
|
||||
const entityState = getEntityStateString(hass, entityInfo);
|
||||
return entityState === "on" ? onValue : offValue;
|
||||
}
|
||||
|
||||
export function getPrinterDevices(hass: HomeAssistant): HassDeviceList {
|
||||
const printers: HassDeviceList = {};
|
||||
for (const key in hass.devices) {
|
||||
const dev = hass.devices[key];
|
||||
|
||||
if (dev.manufacturer === "Anycubic") {
|
||||
printers[dev.id] = dev;
|
||||
}
|
||||
}
|
||||
return printers;
|
||||
}
|
||||
|
||||
export function getPrinterEntities(
|
||||
hass: HomeAssistant,
|
||||
deviceID: string | undefined,
|
||||
): HassEntityInfos {
|
||||
const entities: HassEntityInfos = {};
|
||||
if (deviceID) {
|
||||
for (const key in hass.entities) {
|
||||
const ent = hass.entities[key];
|
||||
|
||||
if (ent.device_id === deviceID) {
|
||||
entities[ent.entity_id] = ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
export function getMatchingEntity(
|
||||
entities: HassEntityInfos,
|
||||
match_domain: string,
|
||||
match_suffix: string,
|
||||
): HassEntityInfo | undefined {
|
||||
for (const key in entities) {
|
||||
const ent = entities[key];
|
||||
const splitID = key.split(".");
|
||||
const domain: string = splitID[0];
|
||||
const entity_id: string = splitID[1];
|
||||
|
||||
if (domain === match_domain && entity_id.endsWith(match_suffix)) {
|
||||
return ent;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getPrinterEntityId(
|
||||
printerEntityIdPart: string | undefined,
|
||||
domain: string,
|
||||
suffix: string,
|
||||
): string {
|
||||
return domain + "." + String(printerEntityIdPart) + suffix;
|
||||
}
|
||||
|
||||
export function getStrictMatchingEntity(
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
match_domain: string,
|
||||
match_suffix: string,
|
||||
): HassEntityInfo | undefined {
|
||||
if (!printerEntityIdPart) {
|
||||
return undefined;
|
||||
}
|
||||
for (const key in entities) {
|
||||
const ent = entities[key];
|
||||
const splitID = key.split(".");
|
||||
const domain: string = splitID[0];
|
||||
const entityIdPart: string = splitID[1].split(printerEntityIdPart)[1];
|
||||
|
||||
if (domain === match_domain && entityIdPart === match_suffix) {
|
||||
return ent;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getPrinterEntityIdPart(
|
||||
entities: HassEntityInfos,
|
||||
): string | undefined {
|
||||
for (const key in entities) {
|
||||
const splitID = key.split(".");
|
||||
const domain: string = splitID[0];
|
||||
const entity_id: string = splitID[1];
|
||||
|
||||
if (domain === "binary_sensor" && entity_id.endsWith("printer_online")) {
|
||||
return entity_id.split("printer_online")[0];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getPrinterSwitchStateObj(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
): HassEntity | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"switch",
|
||||
suffix,
|
||||
);
|
||||
const stateObj = getEntityState(hass, entInfo);
|
||||
return stateObj;
|
||||
}
|
||||
|
||||
export function getPrinterSwitchState(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
onValue: string | boolean = true,
|
||||
offValue: string | boolean = false,
|
||||
): string | boolean | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"switch",
|
||||
suffix,
|
||||
);
|
||||
return entInfo
|
||||
? getEntityStateBinary(hass, entInfo, onValue, offValue)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function getPrinterButtonStateObj(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
defaultState: string | number = "unavailable",
|
||||
defaultAttributes: object = {},
|
||||
): HassEntity {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"button",
|
||||
suffix,
|
||||
);
|
||||
const stateObj = getEntityState(hass, entInfo);
|
||||
return (
|
||||
stateObj ||
|
||||
createEmptyEntity({
|
||||
state: String(defaultState),
|
||||
attributes: defaultAttributes,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getPrinterDryingButtonStateObj(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
): HassEntity {
|
||||
return getPrinterButtonStateObj(
|
||||
hass,
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
suffix,
|
||||
"unavailable",
|
||||
{ duration: 0, temperature: 0 },
|
||||
);
|
||||
}
|
||||
|
||||
export function isPrinterButtonStateAvailable(stateObj: HassEntity): boolean {
|
||||
return !["unavailable"].includes(stateObj.state);
|
||||
}
|
||||
|
||||
export function getPrinterImageStateUrl(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
): string | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"image",
|
||||
suffix,
|
||||
);
|
||||
const stateObj = getEntityState(hass, entInfo);
|
||||
return stateObj ? buildImageUrlFromEntity(stateObj) : undefined;
|
||||
}
|
||||
|
||||
export function getPrinterSensorStateObj(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
defaultState: string | number = "unavailable",
|
||||
defaultAttributes: object = {},
|
||||
): HassEntity {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"sensor",
|
||||
suffix,
|
||||
);
|
||||
const stateObj = getEntityState(hass, entInfo);
|
||||
return (
|
||||
stateObj ||
|
||||
createEmptyEntity({
|
||||
state: String(defaultState),
|
||||
attributes: defaultAttributes,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getPrinterSensorStateString(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
titleCase: boolean = false,
|
||||
): string | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"sensor",
|
||||
suffix,
|
||||
);
|
||||
if (entInfo) {
|
||||
const str = getEntityStateString(hass, entInfo);
|
||||
if (titleCase) {
|
||||
return toTitleCase(str);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPrinterSensorStateFloat(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
): number | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"sensor",
|
||||
suffix,
|
||||
);
|
||||
return entInfo ? getEntityStateFloat(hass, entInfo) : undefined;
|
||||
}
|
||||
|
||||
export function getPrinterBinarySensorState(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
onValue: string | boolean,
|
||||
offValue: string | boolean,
|
||||
undefValue: string | boolean | undefined = undefined,
|
||||
): string | boolean | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"binary_sensor",
|
||||
suffix,
|
||||
);
|
||||
return entInfo
|
||||
? getEntityStateBinary(hass, entInfo, onValue, offValue)
|
||||
: undefValue;
|
||||
}
|
||||
|
||||
export function getPrinterUpdateEntityState(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
suffix: string,
|
||||
): string | undefined {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"update",
|
||||
suffix,
|
||||
);
|
||||
if (entInfo) {
|
||||
return getEntityStateBinary(
|
||||
hass,
|
||||
entInfo,
|
||||
"Update Available",
|
||||
"Up To Date",
|
||||
) as string;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPrinterSupportsMQTT(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
): boolean {
|
||||
const entInfo = getStrictMatchingEntity(
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"binary_sensor",
|
||||
"mqtt_connection_active",
|
||||
);
|
||||
const stateObj = getEntityState(hass, entInfo);
|
||||
return stateObj ? !!stateObj.attributes.supports_mqtt_login : false;
|
||||
}
|
||||
|
||||
export function isFDMPrinter(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
): boolean {
|
||||
return (
|
||||
getPrinterSensorStateObj(
|
||||
hass,
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"current_status",
|
||||
).attributes.material_type === "Filament"
|
||||
);
|
||||
}
|
||||
|
||||
export function isLCDPrinter(
|
||||
hass: HomeAssistant,
|
||||
entities: HassEntityInfos,
|
||||
printerEntityIdPart: string | undefined,
|
||||
): boolean {
|
||||
return (
|
||||
getPrinterSensorStateObj(
|
||||
hass,
|
||||
entities,
|
||||
printerEntityIdPart,
|
||||
"current_status",
|
||||
).attributes.material_type === "Resin"
|
||||
);
|
||||
}
|
||||
|
||||
export function getFileListLocalFilesEntity(
|
||||
entities: HassEntityInfos,
|
||||
): HassEntityInfo | undefined {
|
||||
return getMatchingEntity(entities, "sensor", "file_list_local");
|
||||
}
|
||||
|
||||
export function getFileListLocalRefreshEntity(
|
||||
entities: HassEntityInfos,
|
||||
): HassEntityInfo | undefined {
|
||||
return getMatchingEntity(entities, "button", "request_file_list_local");
|
||||
}
|
||||
|
||||
export function getFileListUdiskFilesEntity(
|
||||
entities: HassEntityInfos,
|
||||
): HassEntityInfo | undefined {
|
||||
return getMatchingEntity(entities, "sensor", "file_list_udisk");
|
||||
}
|
||||
|
||||
export function getFileListUdiskRefreshEntity(
|
||||
entities: HassEntityInfos,
|
||||
): HassEntityInfo | undefined {
|
||||
return getMatchingEntity(entities, "button", "request_file_list_udisk");
|
||||
}
|
||||
|
||||
export function getFileListCloudFilesEntity(
|
||||
entities: HassEntityInfos,
|
||||
): HassEntityInfo | undefined {
|
||||
return getMatchingEntity(entities, "sensor", "file_list_cloud");
|
||||
}
|
||||
|
||||
export function getFileListCloudRefreshEntity(
|
||||
entities: HassEntityInfos,
|
||||
): HassEntityInfo | undefined {
|
||||
return getMatchingEntity(entities, "button", "request_file_list_cloud");
|
||||
}
|
||||
|
||||
export function getPrinterDevID(route: HassRoute): string | undefined {
|
||||
const pathParts = route.path.split("/");
|
||||
return pathParts.length > 1 ? pathParts[1] : undefined;
|
||||
}
|
||||
|
||||
export function getSelectedPrinter(
|
||||
deviceList: HassDeviceList | undefined,
|
||||
deviceID: string | undefined,
|
||||
): HassDevice | undefined {
|
||||
return deviceList && deviceID ? deviceList[deviceID] : undefined;
|
||||
}
|
||||
|
||||
export function getPrinterMAC(printer: HassDevice | undefined): string | null {
|
||||
return printer &&
|
||||
printer.connections.length > 0 &&
|
||||
printer.connections[0].length > 1
|
||||
? printer.connections[0][1]
|
||||
: null;
|
||||
}
|
||||
|
||||
export function getPrinterID(
|
||||
printer: HassDevice | undefined,
|
||||
): string | undefined {
|
||||
return printer ? printer.serial_number : undefined;
|
||||
}
|
||||
|
||||
export function getPage(route: HassRoute): string {
|
||||
const pathParts = route.path.split("/");
|
||||
return pathParts.length > 2 ? pathParts[2] : "main";
|
||||
}
|
||||
|
||||
export function isPrintStatePrinting(printStateString: string): boolean {
|
||||
return [
|
||||
"printing",
|
||||
"preheating",
|
||||
"paused",
|
||||
"downloading",
|
||||
"checking",
|
||||
].includes(printStateString);
|
||||
}
|
||||
|
||||
export function printStateStatusColor(printStateString: string): string {
|
||||
if (printStateString === "preheating") {
|
||||
return "#ffc107";
|
||||
} else if (isPrintStatePrinting(printStateString)) {
|
||||
return "#4caf50";
|
||||
} else if (printStateString === "unknown") {
|
||||
return "#f44336";
|
||||
} else if (
|
||||
printStateString === "operational" ||
|
||||
printStateString === "finished"
|
||||
) {
|
||||
return "#00bcd4";
|
||||
} else {
|
||||
return "#f44336";
|
||||
}
|
||||
}
|
||||
|
||||
export const navigateToPrinter = (
|
||||
node: AnycubicLitNode,
|
||||
printerID: string,
|
||||
replace: boolean = false,
|
||||
): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const prefix: string = node.route.prefix;
|
||||
const endpoint = printerID ? `${printerID}/main` : "";
|
||||
const url = `${prefix}/${endpoint}`;
|
||||
if (replace) {
|
||||
history.replaceState(null, "", url);
|
||||
} else {
|
||||
history.pushState(null, "", url);
|
||||
}
|
||||
fireEvent(window, "location-changed", {
|
||||
replace,
|
||||
});
|
||||
};
|
||||
|
||||
export const navigateToPage = (
|
||||
node: AnycubicLitNode,
|
||||
path: string,
|
||||
replace: boolean = false,
|
||||
): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const prefix: string = node.route.prefix;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const printerID = getPrinterDevID(node.route);
|
||||
const endpoint = printerID ? `${printerID}/${path}` : "";
|
||||
const url = `${prefix}/${endpoint}`;
|
||||
if (replace) {
|
||||
history.replaceState(null, "", url);
|
||||
} else {
|
||||
history.pushState(null, "", url);
|
||||
}
|
||||
fireEvent(window, "location-changed", {
|
||||
replace,
|
||||
});
|
||||
};
|
||||
|
||||
export function milliSecondsToDuration(milliSeconds: number): dfnsDuration {
|
||||
const epoch = new Date(0);
|
||||
const secondsAfterEpoch = new Date(milliSeconds);
|
||||
return dfnsIntervalToDuration({
|
||||
start: epoch,
|
||||
end: secondsAfterEpoch,
|
||||
});
|
||||
}
|
||||
|
||||
export function secondsToDuration(seconds: number): dfnsDuration {
|
||||
return milliSecondsToDuration(seconds * 1e3);
|
||||
}
|
||||
|
||||
export const formatDuration = (
|
||||
time: number | string | undefined,
|
||||
round: boolean,
|
||||
): string => {
|
||||
if (time !== 0 && (!time || isNaN(time as number))) {
|
||||
return "invalid duration";
|
||||
}
|
||||
const dur: dfnsDuration = secondsToDuration(
|
||||
round ? Math.ceil(Number(time) / 60) * 60 : Number(time),
|
||||
);
|
||||
|
||||
const days: string = dur.days && dur.days > 0 ? `${dur.days}d` : "";
|
||||
const hours: string = dur.hours && dur.hours > 0 ? `${dur.hours}h` : "";
|
||||
const minutes: string =
|
||||
dur.minutes && dur.minutes > 0 ? `${dur.minutes}m` : "";
|
||||
const seconds: string =
|
||||
dur.seconds && dur.seconds > 0 ? `${dur.seconds}s` : round ? "" : "0s";
|
||||
|
||||
return `${days}${hours}${minutes}${seconds}`;
|
||||
};
|
||||
|
||||
export const formatFutureTime = (
|
||||
futureSeconds: number | string | undefined,
|
||||
round: boolean,
|
||||
use_24hr: boolean,
|
||||
): string => {
|
||||
if (
|
||||
futureSeconds !== 0 &&
|
||||
(!futureSeconds || isNaN(futureSeconds as number))
|
||||
) {
|
||||
return "invalid time";
|
||||
}
|
||||
const fmtSeconds = round ? "" : ":ss";
|
||||
const fmtString = use_24hr ? `HH:mm${fmtSeconds}` : `h:mm${fmtSeconds} a`;
|
||||
const newDate = new Date();
|
||||
newDate.setSeconds(newDate.getSeconds() + Number(futureSeconds));
|
||||
return dfnsFormat(newDate, fmtString, { in: dfnsUtc });
|
||||
};
|
||||
|
||||
export const calculateTimeStat = (
|
||||
time: number | string | undefined,
|
||||
timeType: CalculatedTimeType,
|
||||
round: boolean = false,
|
||||
use_24hr: boolean = false,
|
||||
): string => {
|
||||
switch (timeType) {
|
||||
case CalculatedTimeType.Remaining:
|
||||
return formatDuration(time, round);
|
||||
case CalculatedTimeType.ETA:
|
||||
return formatFutureTime(time, round, use_24hr);
|
||||
case CalculatedTimeType.Elapsed:
|
||||
return formatDuration(time, round);
|
||||
default:
|
||||
return "<unknown>";
|
||||
}
|
||||
};
|
||||
|
||||
export function getEntityTotalSeconds(
|
||||
timeEntity: HassEntity,
|
||||
isSeconds: boolean = false,
|
||||
): number {
|
||||
let result: number;
|
||||
if (timeEntity.state) {
|
||||
if (timeEntity.state.includes(", ")) {
|
||||
const [days_string, time_string] = timeEntity.state.split(", ");
|
||||
const [hours, minutes, seconds] = time_string.split(":");
|
||||
const day_match = days_string.match(/\d+/);
|
||||
const days = day_match ? day_match[0] : 0;
|
||||
result =
|
||||
+days * 60 * 60 * 24 + +hours * 60 * 60 + +minutes * 60 + +seconds;
|
||||
} else if (timeEntity.state.includes(":")) {
|
||||
const [hours, minutes, seconds] = timeEntity.state.split(":");
|
||||
result = +hours * 60 * 60 + +minutes * 60 + +seconds;
|
||||
} else if (isSeconds) {
|
||||
const seconds = timeEntity.state;
|
||||
result = +seconds;
|
||||
} else {
|
||||
const minutes = timeEntity.state;
|
||||
result = +minutes * 60;
|
||||
}
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const temperatureUnitFromEntity = (
|
||||
entity: HassEntity,
|
||||
): TemperatureUnit => {
|
||||
switch (entity.attributes.unit_of_measurement) {
|
||||
case "°C":
|
||||
return TemperatureUnit.C;
|
||||
case "°F":
|
||||
return TemperatureUnit.F;
|
||||
default:
|
||||
return TemperatureUnit.C;
|
||||
}
|
||||
};
|
||||
|
||||
const temperatureMap = {
|
||||
[TemperatureUnit.C]: {
|
||||
[TemperatureUnit.C]: (t: number): number => t,
|
||||
[TemperatureUnit.F]: (t: number): number => (t * 9.0) / 5.0 + 32.0,
|
||||
},
|
||||
[TemperatureUnit.F]: {
|
||||
[TemperatureUnit.C]: (t: number): number => ((t - 32.0) * 5.0) / 9.0,
|
||||
[TemperatureUnit.F]: (t: number): number => t,
|
||||
},
|
||||
};
|
||||
|
||||
export const convertTemperature = (
|
||||
temperature: number,
|
||||
from: TemperatureUnit,
|
||||
to: TemperatureUnit,
|
||||
): number => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (!temperatureMap[from] || !temperatureMap[from][to]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return temperatureMap[from][to](temperature);
|
||||
};
|
||||
|
||||
export const getEntityTemperature = (
|
||||
temperatureEntity: HassEntity,
|
||||
temperatureUnit: TemperatureUnit | undefined,
|
||||
round: boolean = false,
|
||||
): string => {
|
||||
const t: number = parseFloat(temperatureEntity.state);
|
||||
const u: TemperatureUnit = temperatureUnitFromEntity(temperatureEntity);
|
||||
const tc: number = convertTemperature(t, u, temperatureUnit || u);
|
||||
|
||||
return `${round ? Math.round(tc) : tc.toFixed(2)}°${temperatureUnit || u}`;
|
||||
};
|
||||
|
||||
export function getDefaultMonitoredStats(): PrinterCardStatType[] {
|
||||
return [
|
||||
PrinterCardStatType.Status,
|
||||
PrinterCardStatType.ETA,
|
||||
PrinterCardStatType.Elapsed,
|
||||
PrinterCardStatType.Remaining,
|
||||
];
|
||||
}
|
||||
|
||||
export function getDefaultFDMMonitoredStats(): PrinterCardStatType[] {
|
||||
return [
|
||||
...getDefaultMonitoredStats(),
|
||||
PrinterCardStatType.HotendCurrent,
|
||||
PrinterCardStatType.BedCurrent,
|
||||
PrinterCardStatType.HotendTarget,
|
||||
PrinterCardStatType.BedTarget,
|
||||
];
|
||||
}
|
||||
|
||||
export function getPanelBasicMonitoredStats(): PrinterCardStatType[] {
|
||||
return [
|
||||
...getDefaultMonitoredStats(),
|
||||
PrinterCardStatType.PrinterOnline,
|
||||
PrinterCardStatType.Availability,
|
||||
PrinterCardStatType.ProjectName,
|
||||
PrinterCardStatType.CurrentLayer,
|
||||
];
|
||||
}
|
||||
|
||||
export function getPanelFDMMonitoredStats(): PrinterCardStatType[] {
|
||||
return [
|
||||
...getDefaultFDMMonitoredStats(),
|
||||
PrinterCardStatType.PrinterOnline,
|
||||
PrinterCardStatType.Availability,
|
||||
PrinterCardStatType.ProjectName,
|
||||
PrinterCardStatType.CurrentLayer,
|
||||
];
|
||||
}
|
||||
|
||||
export function getPanelACEMonitoredStats(): PrinterCardStatType[] {
|
||||
return [
|
||||
...getPanelFDMMonitoredStats(),
|
||||
PrinterCardStatType.DryingStatus,
|
||||
PrinterCardStatType.DryingTime,
|
||||
];
|
||||
}
|
||||
|
||||
export function getDefaultCardConfig(): AnycubicCardConfig {
|
||||
return {
|
||||
vertical: false,
|
||||
round: false,
|
||||
use_24hr: true,
|
||||
temperatureUnit: TemperatureUnit.C,
|
||||
monitoredStats: getDefaultMonitoredStats(),
|
||||
scaleFactor: 1,
|
||||
slotColors: [],
|
||||
showSettingsButton: false,
|
||||
alwaysShow: false,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export function undefinedDefault(value: any, defaultValue: any): any {
|
||||
return typeof value === "undefined" ? defaultValue : value;
|
||||
}
|
||||
|
||||
export function speedModesFromStateObj(
|
||||
speedModeState: AnycubicSpeedModeEntity,
|
||||
): AnycubicSpeedModes {
|
||||
const speedModeAttr: AnycubicSpeedMode[] =
|
||||
(speedModeState.attributes.available_modes as
|
||||
| AnycubicSpeedMode[]
|
||||
| undefined) ?? [];
|
||||
return speedModeAttr.reduce(
|
||||
(modes, mode) => ({ ...modes, [mode.mode]: mode.description }),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export function materialTypeFromString(
|
||||
material_type?: string,
|
||||
): AnycubicMaterialType | undefined {
|
||||
return material_type &&
|
||||
(Object.values(AnycubicMaterialType) as string[]).includes(material_type)
|
||||
? AnycubicMaterialType[material_type.toUpperCase() as AnycubicMaterialType]
|
||||
: undefined;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/*
|
||||
* This was pulled AND MODIFIED from the URL below as
|
||||
* LitElements does not prevent the same element from
|
||||
* being registered more than once causing errors.
|
||||
* https://github.com/lit/lit-element/blob/master/src/lib/decorators.ts
|
||||
*
|
||||
* Idea: https://github.com/lit/lit-element/issues/207#issuecomment-1150057355
|
||||
*/
|
||||
|
||||
interface Constructor<T> {
|
||||
// tslint:disable-next-line:no-any
|
||||
new (...args: any[]): T;
|
||||
}
|
||||
|
||||
// From the TC39 Decorators proposal
|
||||
interface ClassElement {
|
||||
kind: "field" | "method";
|
||||
key: PropertyKey;
|
||||
placement: "static" | "prototype" | "own";
|
||||
initializer?: Function;
|
||||
extras?: ClassElement[];
|
||||
finisher?: <T>(clazz: Constructor<T>) => undefined | Constructor<T>;
|
||||
descriptor?: PropertyDescriptor;
|
||||
}
|
||||
|
||||
// From the TC39 Decorators proposal
|
||||
interface ClassDescriptor {
|
||||
kind: "class";
|
||||
elements: ClassElement[];
|
||||
finisher?: <T>(clazz: Constructor<T>) => undefined | Constructor<T>;
|
||||
}
|
||||
|
||||
const legacyCustomElement = (
|
||||
tagName: string,
|
||||
clazz: Constructor<HTMLElement>,
|
||||
): any => {
|
||||
if (window.customElements.get(tagName)) {
|
||||
return clazz as any;
|
||||
}
|
||||
|
||||
window.customElements.define(tagName, clazz);
|
||||
// Cast as any because TS doesn't recognize the return type as being a
|
||||
// subtype of the decorated class when clazz is typed as
|
||||
// `Constructor<HTMLElement>` for some reason.
|
||||
// `Constructor<HTMLElement>` is helpful to make sure the decorator is
|
||||
// applied to elements however.
|
||||
return clazz as any;
|
||||
};
|
||||
|
||||
const standardCustomElement = (
|
||||
tagName: string,
|
||||
descriptor: ClassDescriptor,
|
||||
): any => {
|
||||
const { kind, elements } = descriptor;
|
||||
return {
|
||||
kind,
|
||||
elements,
|
||||
// This callback is called once the class is otherwise fully defined
|
||||
finisher(clazz: Constructor<HTMLElement>): void {
|
||||
if (window.customElements.get(tagName)) {
|
||||
return;
|
||||
}
|
||||
window.customElements.define(tagName, clazz);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Class decorator factory that defines the decorated class as a custom element.
|
||||
*
|
||||
* ```
|
||||
* @customElement('my-element')
|
||||
* class MyElement {
|
||||
* render() {
|
||||
* return html``;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @category Decorator
|
||||
* @param tagName The name of the custom element to define.
|
||||
*/
|
||||
export const customElementIfUndef =
|
||||
(tagName: string): any =>
|
||||
(classOrDescriptor: Constructor<HTMLElement> | ClassDescriptor): any =>
|
||||
typeof classOrDescriptor === "function"
|
||||
? legacyCustomElement(tagName, classOrDescriptor)
|
||||
: standardCustomElement(tagName, classOrDescriptor);
|
||||
@@ -0,0 +1,171 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { Color } from "modern-color";
|
||||
import { hueGradient } from "./lib.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { inputChannelRules } from "./css.js";
|
||||
import { colorEvent } from "./lib.js";
|
||||
|
||||
const labelDictionary = {
|
||||
r: "R (red) channel",
|
||||
g: "G (green) channel",
|
||||
b: "B (blue) channel",
|
||||
h: "H (hue) channel",
|
||||
s: "S (saturation) channel",
|
||||
v: "V (value / brightness) channel",
|
||||
l: "L (luminosity) channel",
|
||||
a: "A (alpha / opacity) channel",
|
||||
};
|
||||
|
||||
export class ColorInputChannel extends LitElement {
|
||||
static properties = {
|
||||
group: { type: String },
|
||||
channel: { type: String },
|
||||
color: { type: Object },
|
||||
isHsl: { type: Boolean },
|
||||
c: { type: Object, state: true, attribute: false },
|
||||
previewGradient: { type: Object, state: true, attribute: false },
|
||||
active: { type: Boolean, state: true, attribute: false },
|
||||
max: { type: Number, state: true, attribute: false },
|
||||
v: { type: Number, state: true, attribute: false },
|
||||
};
|
||||
|
||||
static styles = inputChannelRules;
|
||||
|
||||
clickPreview(e) {
|
||||
const w = 128;
|
||||
const x = Math.max(0, Math.min(e.offsetX, w));
|
||||
let v = Math.round((x / 128) * this.max);
|
||||
if (this.channel === "a") {
|
||||
v = Number((x / 127).toFixed(2));
|
||||
}
|
||||
this.valueChange(null, v);
|
||||
this.setActive(false);
|
||||
}
|
||||
|
||||
valueChange = (e, val = null) => {
|
||||
val = val ?? Number(this.renderRoot.querySelector("input").value);
|
||||
if (this.channel === "a") {
|
||||
val /= 100;
|
||||
}
|
||||
this.c[this.channel] = val;
|
||||
const c = Color.parse(this.c);
|
||||
if (this.group !== "rgb") {
|
||||
c.hsx = this.c;
|
||||
}
|
||||
this.c =
|
||||
this.group === "rgb"
|
||||
? this.color.rgbObj
|
||||
: this.isHsl
|
||||
? this.color.hsl
|
||||
: this.color.hsv;
|
||||
colorEvent(this.renderRoot, c);
|
||||
};
|
||||
|
||||
setActive(active) {
|
||||
this.active = active;
|
||||
if (active) {
|
||||
this.renderRoot.querySelector("input").select();
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
setPreviewGradient() {
|
||||
let c;
|
||||
if (this.group === "rgb") {
|
||||
c = this.color.rgbObj;
|
||||
} else {
|
||||
if (this.color.hsx) {
|
||||
c = this.color.hsx;
|
||||
} else {
|
||||
c = this.isHsl ? this.color.hsl : this.color.hsv;
|
||||
}
|
||||
}
|
||||
this.c = c;
|
||||
const g = this.group;
|
||||
const ch = this.channel;
|
||||
const isAlpha = ch === "a";
|
||||
this.v = c[ch];
|
||||
if (isAlpha) {
|
||||
this.v *= 100;
|
||||
}
|
||||
let max = 255;
|
||||
let minC, maxC;
|
||||
if (g !== "rgb" || ch === "a") {
|
||||
if (ch === "h") {
|
||||
max = this.max = 359;
|
||||
this.previewGradient = {
|
||||
"--preview": `linear-gradient(90deg, ${hueGradient(24, c)})`,
|
||||
"--pct": `${100 * (c.h / max)}%`,
|
||||
};
|
||||
return;
|
||||
} else if (isAlpha) {
|
||||
max = 1;
|
||||
} else {
|
||||
max = 100;
|
||||
}
|
||||
}
|
||||
this.max = max;
|
||||
minC = { ...c };
|
||||
maxC = minC;
|
||||
minC[this.channel] = 0;
|
||||
minC = Color.parse(minC);
|
||||
maxC[this.channel] = max;
|
||||
maxC = Color.parse(maxC);
|
||||
if (this.channel === "l") {
|
||||
const midC = { ...c };
|
||||
midC.l = 50;
|
||||
this.previewGradient = {
|
||||
"--preview": `linear-gradient(90deg, ${minC.hex}, ${Color.parse(midC).hex}, ${maxC.hex})`,
|
||||
"--pct": `${100 * (c[this.channel] / max)}%`,
|
||||
};
|
||||
} else {
|
||||
this.previewGradient = {
|
||||
"--preview": `linear-gradient(90deg, ${isAlpha ? minC.css : minC.hex}, ${isAlpha ? maxC.css : maxC.hex})`,
|
||||
"--pct": `${100 * (c[this.channel] / max)}%`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
willUpdate(props) {
|
||||
this.setPreviewGradient();
|
||||
}
|
||||
|
||||
render() {
|
||||
const chex =
|
||||
this.channel === "a"
|
||||
? html`<div class="transparent-checks"></div>`
|
||||
: null;
|
||||
const max = this.channel === "a" ? 100 : this.max;
|
||||
return html` <div class="${classMap({ active: this.active })}">
|
||||
<label for="channel_${this.ch}">${this.channel.toUpperCase()}</label>
|
||||
<input
|
||||
id="channel_${this.ch}"
|
||||
aria-label="${labelDictionary[this.channel]}"
|
||||
class="form-control"
|
||||
.value="${Math.round(this.v)}"
|
||||
type="number"
|
||||
min="0"
|
||||
max="${max}"
|
||||
@input="${this.valueChange}"
|
||||
@focus="${() => this.setActive(true)}"
|
||||
@blur="${() => this.setActive(false)}"
|
||||
/>
|
||||
<div
|
||||
class="preview-bar"
|
||||
style="${styleMap(this.previewGradient)}"
|
||||
@mousedown="${this.clickPreview}"
|
||||
>
|
||||
<div class="pct"></div>
|
||||
${chex}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get("color-input-channel")) {
|
||||
customElements.define("color-input-channel", ColorInputChannel);
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
// noinspection ES6UnusedImports
|
||||
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { Color, namedColors } from "modern-color";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
// todo: understand why eslint thinks these are unused - they are dependencies
|
||||
import { HueBar } from "./HueBar.js";
|
||||
import { ColorInputChannel } from "./ColorInputChannel.js";
|
||||
import { HSLCanvas } from "./HSLCanvas.js";
|
||||
import {
|
||||
focusedFormControl,
|
||||
formControl,
|
||||
root,
|
||||
transparentChex,
|
||||
} from "./css.js";
|
||||
import { colorEvent, copy } from "./lib.js";
|
||||
import { LitMovable } from "../movable/LitMovable";
|
||||
|
||||
//todo: light/dark mode + get decorators working without typescript
|
||||
export class ColorPicker extends LitElement {
|
||||
static properties = {
|
||||
color: { type: Object, state: true, attribute: false },
|
||||
hex: { type: String, state: true, attribute: false },
|
||||
value: { type: String },
|
||||
isHsl: { type: Boolean, state: true, attribute: false },
|
||||
copied: { type: String },
|
||||
debounceMode: { type: Boolean },
|
||||
buttonDisabled: { attribute: "button-disabled", type: Boolean },
|
||||
};
|
||||
|
||||
static styles = root;
|
||||
|
||||
_color;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._color = Color.parse(namedColors.slateblue);
|
||||
this.isHsl = true;
|
||||
this.buttonDisabled = false;
|
||||
}
|
||||
|
||||
firstUpdated(props) {
|
||||
this.debounceMode = false;
|
||||
if (props.has("value")) {
|
||||
this.color = Color.parse(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
set color(c) {
|
||||
c = c.hsx ? c : c.rgba ? Color.parse(...c.rgba) : Color.parse(c);
|
||||
if (c) {
|
||||
this.hex = c.hex;
|
||||
this._color = c;
|
||||
|
||||
colorEvent(this.renderRoot, c, "colorchanged");
|
||||
}
|
||||
}
|
||||
|
||||
updateColor({ detail: { color } }) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
setColor(e) {
|
||||
const cs = this.renderRoot.querySelector("input#hex").value;
|
||||
const c = Color.parse(cs);
|
||||
|
||||
if (c) {
|
||||
this.color = c;
|
||||
} else {
|
||||
console.log(`ignored unparsable input: ${cs}`);
|
||||
}
|
||||
}
|
||||
|
||||
setHue({ detail: { h } }) {
|
||||
let { s, l, a } = this.color.hsl;
|
||||
if (a === 1) {
|
||||
a = undefined;
|
||||
}
|
||||
this.color = { h, s, l, a };
|
||||
}
|
||||
|
||||
setHsl(hsl) {
|
||||
this.isHsl = hsl;
|
||||
}
|
||||
|
||||
okColor() {
|
||||
colorEvent(this.renderRoot, this.color, "colorpicked");
|
||||
}
|
||||
|
||||
showCopyDialog() {
|
||||
this.copied = null;
|
||||
this.dlg = this.dlg ?? this.renderRoot.querySelector("dialog");
|
||||
if (this.dlg.open) {
|
||||
this.dlg.classList.remove("open");
|
||||
return this.dlg.close();
|
||||
}
|
||||
|
||||
this.dlg.show();
|
||||
this.dlg.classList.add("open");
|
||||
}
|
||||
|
||||
clipboard(f) {
|
||||
const s = this.color.toString(f);
|
||||
window.navigator.clipboard.writeText(s).then(() => {
|
||||
this.hideCopyDialog(s);
|
||||
});
|
||||
}
|
||||
|
||||
hideCopyDialog(copyText) {
|
||||
if (copyText) {
|
||||
this.copied = copyText;
|
||||
setTimeout(() => this.dlg.classList.remove("open"), 400);
|
||||
setTimeout(() => this.hideCopyDialog(), 1200);
|
||||
return;
|
||||
}
|
||||
this.dlg.classList.remove("open");
|
||||
this.dlg.close();
|
||||
this.copied = null;
|
||||
}
|
||||
setSliding({ detail }) {
|
||||
this.debounceMode = detail.sliding;
|
||||
}
|
||||
render() {
|
||||
const hslChannels = this.isHsl ? ["h", "s", "l"] : ["h", "s", "v"];
|
||||
const hsvClass = { button: true, active: !this.isHsl, l: true };
|
||||
const hslClass = { button: true, active: this.isHsl, r: true };
|
||||
const swatchBg = { backgroundColor: this.color };
|
||||
const hideCopied = this.copied
|
||||
? { textAlign: "center", display: "block" }
|
||||
: { display: "none" };
|
||||
const debounceMode = this.debounceMode;
|
||||
return html` <div class="outer">
|
||||
<hue-bar
|
||||
@sliding-hue="${this.setSliding}"
|
||||
hue="${this.color.hsx ? this.color.hsx.h : this.color.hsl.h}"
|
||||
@hue-update="${this.setHue}"
|
||||
.color="${this.color}"
|
||||
></hue-bar>
|
||||
<div class="d-flex">
|
||||
<div class="col w-30">
|
||||
${["r", "g", "b", "a"].map(
|
||||
(c) => html`
|
||||
<color-input-channel
|
||||
group="rgb"
|
||||
channel="${c}"
|
||||
isHsl="${this.isHsl}"
|
||||
.color="${this.color}"
|
||||
@color-update="${this.updateColor}"
|
||||
/>
|
||||
`,
|
||||
)}
|
||||
<div class="hex">
|
||||
<dialog @blur="${() => this.hideCopyDialog()}" tabindex="0">
|
||||
<sub class="copied" style="${styleMap(hideCopied)}"
|
||||
>copied <em>${this.copied}</em></sub
|
||||
>
|
||||
${this.copied
|
||||
? html``
|
||||
: html`
|
||||
<a
|
||||
class="copy-item"
|
||||
@click=${(e) => this.clipboard("hex", e)}
|
||||
id="copyHex"
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
disabled="disabled"
|
||||
value="${this.color.hex}"
|
||||
/>
|
||||
<button
|
||||
title="Copy HEX String"
|
||||
class="button"
|
||||
tabindex="0"
|
||||
>
|
||||
${copy}
|
||||
</button>
|
||||
</a>
|
||||
<a
|
||||
class="copy-item"
|
||||
@click=${(e) => this.clipboard("css", e)}
|
||||
id="copyRgb"
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
disabled="disabled"
|
||||
value="${this.color.css}"
|
||||
/>
|
||||
<button
|
||||
title="Copy RGB String"
|
||||
class="button"
|
||||
tabindex="0"
|
||||
>
|
||||
${copy}
|
||||
</button>
|
||||
</a>
|
||||
<a
|
||||
class="copy-item"
|
||||
id="copyHsl"
|
||||
@click=${(e) =>
|
||||
this.clipboard(
|
||||
this.color.alpha < 1 ? "hsla" : "hsl",
|
||||
e,
|
||||
)}
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
disabled="disabled"
|
||||
value="${this.color.toString(
|
||||
this.color.alpha < 1 ? "hsla" : "hsl",
|
||||
)}"
|
||||
/>
|
||||
<button
|
||||
title="Copy HSL String"
|
||||
class="button"
|
||||
tabindex="0"
|
||||
>
|
||||
${copy}
|
||||
</button>
|
||||
</a>
|
||||
`}
|
||||
</dialog>
|
||||
<label for="hex">#</label>
|
||||
<input
|
||||
aria-label="Hexadecimal value (editable - accepts any valid color string)"
|
||||
@input="${this.setColor}"
|
||||
class="form-control"
|
||||
id="hex"
|
||||
placeholder="Set color"
|
||||
value="${this.hex}"
|
||||
/><a
|
||||
title="Show copy to clipboard menu"
|
||||
@click="${this.showCopyDialog}"
|
||||
class="button copy"
|
||||
>
|
||||
${copy}
|
||||
<span>⯅</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col w-30">
|
||||
${hslChannels.map(
|
||||
(c) => html`
|
||||
<color-input-channel
|
||||
group="hsl"
|
||||
channel="${c}"
|
||||
.isHsl="${this.isHsl}"
|
||||
.color="${this.color}"
|
||||
@color-update="${this.updateColor}"
|
||||
/>
|
||||
`,
|
||||
)}
|
||||
<div class="hsl-mode">
|
||||
<a
|
||||
title="Use hue / saturation / value (brightness) mode"
|
||||
class="${classMap(hsvClass)}"
|
||||
@click="${() => this.setHsl(false)}"
|
||||
>HSV</a
|
||||
><a
|
||||
title="Use hue / saturation / luminosity mode"
|
||||
class="${classMap(hslClass)}"
|
||||
@click="${() => this.setHsl(true)}"
|
||||
>HSL</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<hsl-canvas
|
||||
.debounceMode="${debounceMode}"
|
||||
size="${160}"
|
||||
.isHsl="${this.isHsl}"
|
||||
.color="${this.color}"
|
||||
@color-update="${this.updateColor}"
|
||||
></hsl-canvas>
|
||||
<div class="ok">
|
||||
<a
|
||||
class="button"
|
||||
.disabled=${this.buttonDisabled}
|
||||
@click="${this.okColor}"
|
||||
>OK
|
||||
<span class="swatch">
|
||||
<span style="${styleMap(swatchBg)}"></span>
|
||||
<span class="checky"></span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.customElements.get("color-picker")) {
|
||||
window.customElements.define("color-picker", ColorPicker);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { Color } from "modern-color";
|
||||
import { colorEvent } from "./lib.js";
|
||||
|
||||
export class HSLCanvas extends LitElement {
|
||||
static properties = {
|
||||
color: { type: Object },
|
||||
isHsl: { type: Boolean },
|
||||
size: { type: Number },
|
||||
debounceMode: { type: Boolean },
|
||||
ctx: { type: Object, state: true, attribute: false },
|
||||
hsw: { type: Object, state: true, attribute: false },
|
||||
circlePos: { type: Object, state: true, attribute: false },
|
||||
};
|
||||
static styles = css`
|
||||
:host .outer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
:host .outer canvas {
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host .circle {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border: solid 2px #eee;
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 0 3px #000,
|
||||
inset 0 0 1px #fff;
|
||||
position: absolute;
|
||||
margin: -8px;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
`;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isHsl = true;
|
||||
this.circlePos = { top: 0, left: 0, bounds: { x: "", y: "" } };
|
||||
this.size = 160;
|
||||
}
|
||||
|
||||
setColor(c) {
|
||||
//this.color = c;
|
||||
colorEvent(this.renderRoot, c);
|
||||
}
|
||||
|
||||
setCircleCss(x, y) {
|
||||
const left = `${x}`;
|
||||
const top = `${y}`;
|
||||
const bounds = { x: `0, ${this.size}`, y: `0,${this.size}` };
|
||||
//let bounds = {x: `${-x}, ${this.size-x}`,y:`${-y},${this.size-y}`}
|
||||
this.circlePos = { top, left, bounds };
|
||||
}
|
||||
|
||||
pickCoord({ offsetX, offsetY }) {
|
||||
const x = offsetX;
|
||||
const y = offsetY;
|
||||
const { size, hsw, isHsl, color } = this;
|
||||
|
||||
let w = (size - y) / size;
|
||||
w = Math.round(w * 100);
|
||||
const sat = Math.round((x / size) * 100);
|
||||
const hsx = { h: hsw.h, s: sat, [isHsl ? "l" : "v"]: w };
|
||||
|
||||
const c = isHsl ? Color.fromHsl(hsx) : Color.fromHsv(hsx);
|
||||
this.setCircleCss(x, y);
|
||||
c.a = color.alpha;
|
||||
c.hsx = hsx;
|
||||
c.fromHSLCanvas = true;
|
||||
this.setColor(c);
|
||||
}
|
||||
|
||||
debouncePaintDetail(hsx) {
|
||||
clearTimeout(this.bouncer);
|
||||
this.bouncer = setTimeout(() => this.paintHSL(hsx, true), 50);
|
||||
this.paintHSL(hsx, false);
|
||||
}
|
||||
|
||||
// todo: test assumption that this perf lag (lit warning)
|
||||
// is ok due to rendering canvas post update
|
||||
paintHSL(hsx, detail = null) {
|
||||
if (this.debounceMode && detail === null) {
|
||||
// enable rapid painting in lower res
|
||||
return this.debouncePaintDetail(hsx);
|
||||
}
|
||||
const { ctx, color, isHsl, size } = this;
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
//console.time('paint'+detail)
|
||||
|
||||
const clr = color;
|
||||
hsx = (hsx ?? isHsl) ? clr.hsl : clr.hsv; // hue-sat-whatever
|
||||
hsx.w = isHsl ? hsx.l : hsx.v;
|
||||
const { h, s, w } = hsx;
|
||||
const hsw = (this.hsw = { h, s, w });
|
||||
const scale = size / 100;
|
||||
const fillHsl = (h, s, l) => `hsl(${h}, ${s}%, ${100 - l}%)`;
|
||||
const fillHsv = (h, s, v) => Color.fromHsv({ h, s, v: 100 - v }).hex;
|
||||
const fill = isHsl ? fillHsl : fillHsv;
|
||||
|
||||
const incr = detail === false ? 4 : 1; //rapid painting during hue slider ops
|
||||
for (let s = 0; s < 100; s += incr) {
|
||||
for (let w = 0; w < 100; w += incr) {
|
||||
ctx.fillStyle = fill(h, s, w);
|
||||
ctx.fillRect(s, w, s + incr, w + incr);
|
||||
}
|
||||
}
|
||||
|
||||
this.setCircleCss(hsw.s * scale, size - hsx.w * scale);
|
||||
//console.timeEnd('paint'+detail)
|
||||
}
|
||||
|
||||
willUpdate(props) {
|
||||
if (props.has("color") || props.has("isHsl")) {
|
||||
if (this.color?.hsx) {
|
||||
if (this.color.fromHSLCanvas) {
|
||||
delete this.color.fromHSLCanvas; //avoid extra paint job
|
||||
return;
|
||||
}
|
||||
return this.paintHSL(this.color.hsx);
|
||||
}
|
||||
this.paintHSL();
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated(props) {
|
||||
const canvas = this.renderRoot.querySelector("canvas");
|
||||
this.ctx = canvas.getContext("2d");
|
||||
this.paintHSL();
|
||||
}
|
||||
|
||||
circleMove({ posTop: offsetY, posLeft: offsetX }) {
|
||||
this.pickCoord({ offsetX, offsetY });
|
||||
}
|
||||
|
||||
render() {
|
||||
const hw = { height: this.size + "p", width: this.size + "px" };
|
||||
const { top, left, bounds } = this.circlePos;
|
||||
return html` <div
|
||||
class="outer"
|
||||
@click="${this.pickCoord}"
|
||||
style="${styleMap(hw)}"
|
||||
>
|
||||
<canvas height="100" width="100"></canvas>
|
||||
<lit-movable
|
||||
boundsX="${bounds.x}"
|
||||
boundsY="${bounds.y}"
|
||||
posTop="${top}"
|
||||
posLeft="${left}"
|
||||
.onmove="${(e) => this.circleMove(e)}"
|
||||
>
|
||||
<div class="circle"></div>
|
||||
</lit-movable>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get("hsl-canvas")) {
|
||||
customElements.define("hsl-canvas", HSLCanvas);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { LitElement, html, css, unsafeCSS } from "lit";
|
||||
import { Color } from "modern-color";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { colorEvent, hueGradient } from "./lib.js";
|
||||
|
||||
export class HueBar extends LitElement {
|
||||
static properties = {
|
||||
hue: { type: Number },
|
||||
color: { type: Object },
|
||||
gradient: { type: String, attribute: false },
|
||||
sliderStyle: { type: String, attribute: false },
|
||||
sliderBounds: { type: Object },
|
||||
width: { type: Number, attribute: false },
|
||||
};
|
||||
static styles = css`
|
||||
:host > div {
|
||||
display: block;
|
||||
width: ${unsafeCSS(this.width)}px;
|
||||
height: 15px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host .slider {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
height: 17px;
|
||||
width: 8px;
|
||||
margin-left: -4px;
|
||||
box-shadow:
|
||||
0 0 3px #111,
|
||||
inset 0 0 2px white;
|
||||
}
|
||||
`;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.gradient = {
|
||||
backgroundImage: `linear-gradient(90deg, ${hueGradient(24)})`,
|
||||
};
|
||||
this.width = 400;
|
||||
this.sliderStyle = { display: "none" };
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
const me = this.renderRoot.querySelector("lit-movable");
|
||||
me.onmovestart = () => {
|
||||
colorEvent(this.renderRoot, { sliding: true }, "sliding-hue");
|
||||
};
|
||||
me.onmoveend = () => {
|
||||
colorEvent(this.renderRoot, { sliding: false }, "sliding-hue");
|
||||
};
|
||||
me.onmove = ({ posLeft }) => this.selectHue({ offsetX: posLeft });
|
||||
this.sliderStyle = this.sliderCss(this.hue);
|
||||
}
|
||||
|
||||
get sliderBounds() {
|
||||
const r = this.width / 360;
|
||||
const posLeft = Number(this.hue) * r;
|
||||
const min = 0 - posLeft;
|
||||
const max = this.width - posLeft;
|
||||
return { min, max, posLeft };
|
||||
}
|
||||
get sliderCss() {
|
||||
return (h) => {
|
||||
if (this.color.hsx) {
|
||||
h = this.color.hsx.h;
|
||||
}
|
||||
if (h === undefined) {
|
||||
h = this.color.hsl.h;
|
||||
}
|
||||
const color = Color.parse({ h, s: 100, l: 50 });
|
||||
|
||||
return { backgroundColor: color.css };
|
||||
};
|
||||
}
|
||||
|
||||
willUpdate(props) {
|
||||
const h = props.get("hue");
|
||||
if (h && isFinite(this.hue)) {
|
||||
if (this.color?.hsx) {
|
||||
return; // console.log({hueBarIgnored: this.color.hsx});
|
||||
}
|
||||
const hue = this.hue;
|
||||
this.sliderStyle = this.sliderCss(hue);
|
||||
}
|
||||
}
|
||||
|
||||
selectHue(e) {
|
||||
const r = 360 / this.width;
|
||||
const l = e.offsetX;
|
||||
const h = Math.max(0, Math.min(359, Math.round(l * r)));
|
||||
const target = this.renderRoot.querySelector("a");
|
||||
const event = new CustomEvent("hue-update", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { h },
|
||||
});
|
||||
|
||||
target.dispatchEvent(event);
|
||||
this.sliderStyle = this.sliderCss(h);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <div
|
||||
style=${styleMap(this.gradient)}
|
||||
class="bar"
|
||||
@click="${this.selectHue}"
|
||||
>
|
||||
<lit-movable
|
||||
horizontal="${this.sliderBounds.min}, ${this.sliderBounds.max}"
|
||||
posLeft="${this.sliderBounds.posLeft}"
|
||||
>
|
||||
<a class="slider" style=${styleMap(this.sliderCss(this.h))}></a>
|
||||
</lit-movable>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get("hue-bar")) {
|
||||
customElements.define("hue-bar", HueBar);
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
import { css } from "lit";
|
||||
export const transparentChex = css`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
rgba(0, 0, 0, 0.125) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgba(0, 0, 0, 0.125) 0,
|
||||
rgba(0, 0, 0, 0.125) 0
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
rgba(0, 0, 0, 0.125) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgba(0, 0, 0, 0.125) 0,
|
||||
rgba(0, 0, 0, 0.125) 0
|
||||
),
|
||||
#fff;
|
||||
background-repeat: repeat, repeat;
|
||||
background-position:
|
||||
0 0,
|
||||
6px 6px;
|
||||
background-size:
|
||||
12px 12px,
|
||||
12px 12px;
|
||||
`;
|
||||
|
||||
export const formControl = css`
|
||||
display: inline-block;
|
||||
width: 69px;
|
||||
padding: 0.325rem 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: var(--input-color);
|
||||
appearance: none;
|
||||
background-color: var(--input-bg);
|
||||
background-clip: padding-box;
|
||||
border: 1px solid var(--form-border-color);
|
||||
border-radius: 3px;
|
||||
transition:
|
||||
border-color 0.15s ease-in-out,
|
||||
box-shadow 0.15s ease-in-out;
|
||||
`;
|
||||
export const focusedFormControl = css`
|
||||
color: var(--input-active-color);
|
||||
background-color: var(--input-active-bg);
|
||||
border-color: var(--input-active-border-color);
|
||||
outline: 0;
|
||||
box-shadow: var(--input-active-box-shadow);
|
||||
`;
|
||||
|
||||
export const root = css`
|
||||
:host {
|
||||
--font-fam: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
"Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bg-color: rgb(30 41 59);
|
||||
--label-color: #ccc;
|
||||
--form-border-color: #495057;
|
||||
--input-active-border-color: #86b7fe;
|
||||
--input-bg: #020617;
|
||||
--input-active-bg: #4682b4;
|
||||
--input-color: #ccc;
|
||||
--input-active-color: #333;
|
||||
--input-active-box-shadow: 0 2px 5px #ccc;
|
||||
--button-active-bg: #0c5b9d;
|
||||
--button-active-color: white;
|
||||
--outer-box-shadow: 0 4px 12px #111;
|
||||
}
|
||||
:host > .outer {
|
||||
position: relative;
|
||||
background-color: var(--bg-color);
|
||||
height: 250px;
|
||||
width: 400px;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
box-shadow: var(--outer-box-shadow);
|
||||
}
|
||||
.d-flex {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.w-30 {
|
||||
width: 30%;
|
||||
}
|
||||
.w-40 {
|
||||
width: 40%;
|
||||
position: relative;
|
||||
height: 210px;
|
||||
}
|
||||
:host .form-control {
|
||||
${formControl}
|
||||
}
|
||||
:host .form-control:focus {
|
||||
${focusedFormControl}
|
||||
}
|
||||
:host label {
|
||||
width: 12px;
|
||||
display: inline-block;
|
||||
color: var(--label-color);
|
||||
font-family: var(--font-fam);
|
||||
}
|
||||
:host .hsl-mode {
|
||||
padding-left: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
:host .button {
|
||||
padding: 0.325rem 0.5rem;
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--form-border-color);
|
||||
font-family: var(--font-fam);
|
||||
color: var(--input-color);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
:host div.hex {
|
||||
margin-top: 27px;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
:host dialog {
|
||||
opacity: 0;
|
||||
width: 177px;
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
left: 0px;
|
||||
z-index: 3;
|
||||
border: 1px solid transparent;
|
||||
outline: transparent;
|
||||
box-shadow: var(--outer-box-shadow);
|
||||
background-color: var(--input-bg);
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
:host dialog.open {
|
||||
opacity: 1;
|
||||
}
|
||||
:host dialog * {
|
||||
color: var(--input-color);
|
||||
}
|
||||
:host dialog a.copy-item {
|
||||
margin-bottom: 5px;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
width: 180px;
|
||||
cursor: pointer;
|
||||
}
|
||||
:host dialog input.form-control {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 132px;
|
||||
padding-bottom: 2px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
:host dialog button.button {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: -5px;
|
||||
font-size: 12px;
|
||||
height: 27px;
|
||||
width: 27px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
:host dialog a.copy-item:hover .button,
|
||||
:host dialog a.copy-item:hover input.form-control,
|
||||
:host dialog a.copy-item:hover path {
|
||||
color: var(--button-active-color);
|
||||
background-color: var(--button-active-bg);
|
||||
fill: var(--button-active-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
:host dialog .button svg {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-left: -3px;
|
||||
}
|
||||
:host div.hex input {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
:host .button.copy {
|
||||
padding: 8px 6px 5px 5px;
|
||||
position: relative;
|
||||
position: relative;
|
||||
border-left: 0;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
height: 34px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
:host .button.copy svg {
|
||||
height: 16px;
|
||||
width: 15px;
|
||||
margin-right: -2px;
|
||||
}
|
||||
:host .button.copy span {
|
||||
font-size: 10px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
:host a.button.l {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
:host a.button.r {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-left: none;
|
||||
}
|
||||
:host a.button.active {
|
||||
color: #eee;
|
||||
background-color: var(--button-active-bg);
|
||||
cursor: default;
|
||||
}
|
||||
:host .ok {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
:host .ok a {
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
:host .swatch {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
:host .swatch span {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
:host .swatch span.checky {
|
||||
${transparentChex}
|
||||
z-index: 0;
|
||||
}
|
||||
`;
|
||||
export const inputChannelRules = css`
|
||||
:host > div {
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host label {
|
||||
width: 12px;
|
||||
display: inline-block;
|
||||
color: var(--label-color);
|
||||
font-family: var(--font-fam);
|
||||
}
|
||||
|
||||
:host .form-control {
|
||||
${formControl}
|
||||
}
|
||||
|
||||
:host .form-control:focus {
|
||||
${focusedFormControl}
|
||||
}
|
||||
|
||||
:host .preview-bar {
|
||||
height: 4px;
|
||||
width: 85.5px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 17.5px;
|
||||
--pct: 0;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
:host .preview-bar:after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
background-image: var(--preview);
|
||||
background-color: transparent;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
box-shadow: inset 0 -1px 1px var(--form-border-color);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host > div.active .preview-bar {
|
||||
width: 128px;
|
||||
bottom: -23px;
|
||||
right: -9px;
|
||||
height: 10px;
|
||||
border: 8px solid var(--input-bg);
|
||||
box-shadow: var(--input-active-box-shadow);
|
||||
pointer-events: all;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
:host > div.active .preview-bar:after {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
:host .preview-bar .pct {
|
||||
bottom: -3px;
|
||||
margin-top: -0.75px;
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 11px;
|
||||
background: 0 0;
|
||||
left: var(--pct);
|
||||
display: inline-block;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host .preview-bar .pct:before {
|
||||
content: "";
|
||||
height: 7px;
|
||||
width: 5px;
|
||||
position: absolute;
|
||||
left: -2.5px;
|
||||
top: 2.5px;
|
||||
background-color: #fff;
|
||||
clip-path: polygon(50% 0, 100% 100%, 0 100%);
|
||||
}
|
||||
:host .active .preview-bar .pct:before {
|
||||
width: 7px;
|
||||
height: 11px;
|
||||
left: -3.5px;
|
||||
top: -1px;
|
||||
}
|
||||
:host .transparent-checks {
|
||||
${transparentChex}
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
:host div.active .transparent-checks {
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Color } from "modern-color";
|
||||
import { html } from "lit";
|
||||
|
||||
export const colorEvent = (target, color, name = "color-update") => {
|
||||
const detail = name.includes("color") ? { color } : color;
|
||||
const event = new CustomEvent(name, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail,
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
export const hueGradient = (gran = 3, hsx) => {
|
||||
//todo: update to take optional hsx(v/l) vals and compose
|
||||
let h = 0;
|
||||
let s = 100;
|
||||
let l = 50;
|
||||
let v = null;
|
||||
let isHsv = false;
|
||||
if (hsx) {
|
||||
s = hsx.s;
|
||||
if (hsx.hasOwnProperty("v")) {
|
||||
v = hsx.v;
|
||||
l = null;
|
||||
isHsv = true;
|
||||
} else {
|
||||
l = hsx.l;
|
||||
}
|
||||
}
|
||||
const stops = [];
|
||||
let color, pos;
|
||||
const cs = (color, pos) => `${color.css} ${(pos * 100).toFixed(1)}%`;
|
||||
while (h < 360) {
|
||||
color = Color.parse(isHsv ? { h, s, v } : { h, s, l });
|
||||
pos = h / 360;
|
||||
stops.push(cs(color, pos));
|
||||
h += gran;
|
||||
}
|
||||
h = 359;
|
||||
color = Color.parse(isHsv ? { h, s, v } : { h, s, l });
|
||||
pos = 1;
|
||||
stops.push(cs(color, pos));
|
||||
return stops.join(", ");
|
||||
};
|
||||
|
||||
export const copy = html`<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M13 7H7V5H13V7Z" fill="currentColor"></path>
|
||||
<path d="M13 11H7V9H13V11Z" fill="currentColor"></path>
|
||||
<path d="M7 15H13V13H7V15Z" fill="currentColor"></path>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3 19V1H17V5H21V23H7V19H3ZM15 17V3H5V17H15ZM17 7V19H9V21H19V7H17Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>`;
|
||||
@@ -0,0 +1,478 @@
|
||||
import { LitElement, html } from "lit";
|
||||
|
||||
const pxVal = (v) =>
|
||||
isFinite(v) ? Number(v) : Number(v.replace(/[^0-9.\-]/g, ""));
|
||||
|
||||
const zeroIfNaN = (v) => {
|
||||
v = Number(v);
|
||||
if (isNaN(v) || [undefined, null].includes(v)) {
|
||||
v = 0;
|
||||
}
|
||||
return v;
|
||||
};
|
||||
class Coord {
|
||||
constructor(x, y) {
|
||||
this.x = zeroIfNaN(x);
|
||||
this.y = zeroIfNaN(y);
|
||||
}
|
||||
static fromPointerEvent(event) {
|
||||
const { pageX, pageY } = event;
|
||||
return new Coord(pageX, pageY);
|
||||
}
|
||||
static fromElementStyle(el) {
|
||||
const x = pxVal(el.style.left ?? 0);
|
||||
const y = pxVal(el.style.top ?? 0);
|
||||
|
||||
return new Coord(x, y);
|
||||
}
|
||||
static fromObject({ x, y }) {
|
||||
return new Coord(x, y);
|
||||
}
|
||||
get top() {
|
||||
return this.y;
|
||||
}
|
||||
set top(v) {
|
||||
this.y = v;
|
||||
}
|
||||
get left() {
|
||||
return this.x;
|
||||
}
|
||||
set left(v) {
|
||||
this.x = v;
|
||||
}
|
||||
}
|
||||
|
||||
const getClickOffset = (event) => {
|
||||
const coords = Coord.fromPointerEvent(event);
|
||||
const off = event.target.getBoundingClientRect();
|
||||
const x = coords.x - (off.left + document.body.scrollLeft);
|
||||
const y = coords.y - (off.top + document.body.scrollTop);
|
||||
return new Coord(x, y);
|
||||
};
|
||||
class MoveBounds {
|
||||
constructor(min = -Infinity, max = Infinity) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.attr = "";
|
||||
}
|
||||
get constrained() {
|
||||
return this.min === this.max;
|
||||
}
|
||||
get unconstrained() {
|
||||
return this.min === -Infinity && this.max === Infinity;
|
||||
}
|
||||
static fromString(s = null, offset = 0) {
|
||||
if (!s) {
|
||||
return new MoveBounds();
|
||||
}
|
||||
if (s === "null") {
|
||||
return new MoveBounds(0, 0);
|
||||
}
|
||||
const [min, max] = s.split(",").map((n) => Number(n.trim()) + offset);
|
||||
const bounds = new MoveBounds(min, max);
|
||||
bounds.attr = s;
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @attr {number} posTop - Represents the offsetTop value (reflected). When set, will set the initial style.top value. Updates with move events
|
||||
* @attr {number} posLeft - Represents the offsetLeft value (reflected). When set, will set the initial style.top value. Updates with move events
|
||||
* @attr {string} targetSelector - A selector to select the element that will move. Defaults to the lit-movable (this) element, but useful when for example you want to allow a modal header to respond to pointer events but you want the entire modal to move.
|
||||
* @attr {string} boundsX - Set to boundsX="min,max" to restrict movement along the x axis
|
||||
* @attr {string} boundsY - Set to boundsY="min,max" to restrict movement along the y axis
|
||||
* @attr {string} vertical - Will constrain horizontal (x) movement completely and allow vertical (y) movement between the specified values
|
||||
* @attr {string} horizontal - Will constrain vertical (y) movement completely and allow horizontal (x) movement between the specified values
|
||||
* @attr {number} grid - Snaps movement to nearest grid position. Initial element position represents the 0,0 position. Movement snapped to the provided value increment
|
||||
* @attr {boolean} shiftBehavior - When enabled, holding the shift key will coerce movement to perpendicular coordinates only.
|
||||
* @attr {boolean} disabled - Disables movement behavior
|
||||
* @attr {boolean} eventsOnly - Only fires movement events, but will not move the element
|
||||
*
|
||||
* @slot - default/unnamed slot
|
||||
*
|
||||
* @prop {object} target - The target element that will move
|
||||
* @prop {object} bounds - Computed from the specified boundsX, boundsY attributes. Represents the runtime movement constraints if any
|
||||
*
|
||||
* @fires onmovestart - Initial state when user initiates a move operation (onpointerdown). Bind syntax: element.onmovestart=(state)=>console.log(state).
|
||||
* @fires onmove - Fires continuously after onpointerdown until document.onpointerup event. Bind syntax: element.onmove=(state)=>console.log(state).
|
||||
* @fires onmovestart - Final state when user completes a move operation (document.onpointerup). Bind syntax: element.onmoveend=(state)=>console.log(state).
|
||||
*
|
||||
* @event {CustomEvent} movestart - Initial state when user initiates a move operation (onpointerdown). * Bind with element.addEventListener('movestart', ({detail}) => console.log({moveState:detail}))
|
||||
* @event {CustomEvent} move - Fires continuously after onpointerdown until document.onpointerup event. Bind with element.addEventListener('move', ({detail}) => console.log({moveState:detail}))
|
||||
* @event {CustomEvent} moveend - Final state when user completes a move operation (document.onpointerup). Bind with element.addEventListener('moveend', ({detail}) => console.log({moveState:detail}))
|
||||
*
|
||||
* @summary A Lit 3 wrapper web component that can enable robustly customizable element move operations and expose rich state data.
|
||||
*
|
||||
* @tag lit-movable
|
||||
*/
|
||||
|
||||
export class LitMovable extends LitElement {
|
||||
_target;
|
||||
_targetSelector = null;
|
||||
_boundsX = new MoveBounds();
|
||||
_boundsY = new MoveBounds();
|
||||
isMoving = false;
|
||||
moveState = {};
|
||||
_vertical = null;
|
||||
_horizontal = null;
|
||||
_posTop = null;
|
||||
_posLeft = null;
|
||||
_grid = 1;
|
||||
pointerId;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
get vertical() {
|
||||
return this._vertical;
|
||||
}
|
||||
set vertical(v) {
|
||||
this.boundsY = v;
|
||||
this.boundsX = "null";
|
||||
this._vertical = v;
|
||||
}
|
||||
get horizontal() {
|
||||
return this._horizontal;
|
||||
}
|
||||
set horizontal(v) {
|
||||
this.boundsX = v;
|
||||
this.boundsY = "null";
|
||||
this._horizontal = v;
|
||||
}
|
||||
|
||||
set posTop(v) {
|
||||
v = Number(v);
|
||||
this._posTop = v;
|
||||
if (this.target) {
|
||||
this.target.style.top = v + "px";
|
||||
}
|
||||
}
|
||||
get posTop() {
|
||||
return this._posTop;
|
||||
}
|
||||
|
||||
set posLeft(v) {
|
||||
v = Number(v);
|
||||
this._posLeft = v;
|
||||
if (this.target) {
|
||||
this.target.style.left = v + "px";
|
||||
}
|
||||
}
|
||||
get posLeft() {
|
||||
return this._posLeft;
|
||||
}
|
||||
|
||||
get grid() {
|
||||
return this._grid;
|
||||
}
|
||||
set grid(v) {
|
||||
if (v > 0 && v < Infinity) {
|
||||
this._grid = v;
|
||||
} else {
|
||||
this._grid = 1;
|
||||
}
|
||||
}
|
||||
get bounds() {
|
||||
return {
|
||||
left: this._boundsX,
|
||||
top: this._boundsY,
|
||||
};
|
||||
}
|
||||
|
||||
set targetSelector(v) {
|
||||
this._targetSelector = v;
|
||||
this._retryTarget = document.querySelector(v) === null;
|
||||
this._target = document.querySelector(v);
|
||||
}
|
||||
get targetSelector() {
|
||||
return this._targetSelector;
|
||||
}
|
||||
|
||||
get target() {
|
||||
return this._target ?? this;
|
||||
}
|
||||
|
||||
set target(v) {
|
||||
this._target = v;
|
||||
}
|
||||
|
||||
get boundsX() {
|
||||
return this._boundsX;
|
||||
}
|
||||
|
||||
set boundsX(v) {
|
||||
this._boundsX = MoveBounds.fromString(
|
||||
v,
|
||||
pxVal(this.target?.style.left ?? 0),
|
||||
);
|
||||
this.bounds.left = this._boundsX;
|
||||
}
|
||||
|
||||
get boundsY() {
|
||||
return this._boundsY;
|
||||
}
|
||||
|
||||
set boundsY(v) {
|
||||
this._boundsY = MoveBounds.fromString(
|
||||
v,
|
||||
pxVal(this.target?.style.top ?? 0),
|
||||
);
|
||||
//let offsetTop =
|
||||
this.bounds.top = this._boundsY;
|
||||
}
|
||||
|
||||
static properties = {
|
||||
//set the left/top position
|
||||
// defaults to element.offsetTop /offsetLeft
|
||||
posLeft: { type: Number },
|
||||
posTop: { type: Number },
|
||||
|
||||
// target element that moves - defaults to root element
|
||||
target: { type: Object, attribute: false, state: true },
|
||||
|
||||
// selector that will set the target element that will move
|
||||
targetSelector: { type: String },
|
||||
|
||||
// object (left:boundsX,top:boundsY)
|
||||
bounds: { type: Object, attribute: false, state: true },
|
||||
|
||||
// Both x and y default to -Infinity,Infinity.
|
||||
// Set to boundsX="min,max" ([0,0] to restrict the axis)
|
||||
// these are attribute string setters meant for declarative
|
||||
// element attribute setting
|
||||
boundsX: { type: String },
|
||||
boundsY: { type: String },
|
||||
|
||||
// vertical="min,max" - constrain movement to y axis within min and max numbers provided.
|
||||
// automatically disables horizontal movement
|
||||
vertical: { type: String },
|
||||
|
||||
// horizontal="min,max" - constrain movement to x axis within min and max provided.
|
||||
// automatically disables vertical movement
|
||||
horizontal: { type: String },
|
||||
|
||||
//defaults to 1. snap to grid size in pixels.
|
||||
grid: { type: Number },
|
||||
|
||||
// set to true enables shift key to constrain movement to either
|
||||
// x or y axis (whichever is greater).
|
||||
// Setting any bounds option automatically disables shift key behavior.
|
||||
shiftBehavior: { type: Boolean },
|
||||
|
||||
//disables moving
|
||||
disabled: { type: Boolean },
|
||||
|
||||
// advanced mode: Does not move the element, but fires
|
||||
// events so you can pass to your own handler
|
||||
eventsOnly: { type: Boolean },
|
||||
listening: { type: Boolean },
|
||||
onmovestart: { type: Object },
|
||||
onmoveend: { type: Object },
|
||||
onmove: { type: Object },
|
||||
};
|
||||
|
||||
firstUpdated(props) {
|
||||
if (this._retryTarget) {
|
||||
// element wasn't loaded
|
||||
this.target = document.querySelector(this.targetSelector);
|
||||
}
|
||||
const { bounds, target, posTop, posLeft } = this;
|
||||
|
||||
const {
|
||||
offsetLeft,
|
||||
offsetTop,
|
||||
style: { left, top },
|
||||
} = this.target;
|
||||
target.classList.add("--movable-base");
|
||||
this.renderRoot.addEventListener("pointerdown", (e) => this.pointerdown(e));
|
||||
|
||||
target.style.position = "absolute";
|
||||
target.style.cursor = "pointer";
|
||||
|
||||
if (posLeft) {
|
||||
target.style.left = posLeft + "px";
|
||||
} else if (!left && offsetLeft) {
|
||||
target.style.left = offsetLeft + "px";
|
||||
if (bounds.left.constrained) {
|
||||
bounds.left.min = bounds.left.max = offsetLeft;
|
||||
}
|
||||
}
|
||||
if (posTop) {
|
||||
target.style.top = posTop + "px";
|
||||
} else if (!top && offsetTop) {
|
||||
target.style.top = offsetTop + "px";
|
||||
if (bounds.top.constrained) {
|
||||
bounds.top.min = bounds.top.max = offsetTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reposition(pos) {
|
||||
if (typeof pos === "object") {
|
||||
const { eventsOnly, target } = this;
|
||||
this.posTop = pos.top;
|
||||
this.posLeft = pos.left;
|
||||
if (target && !eventsOnly) {
|
||||
target.style.left = pos.left + "px";
|
||||
target.style.top = pos.top + "px";
|
||||
}
|
||||
} else {
|
||||
this.isMoving = pos;
|
||||
}
|
||||
}
|
||||
|
||||
moveInit(event) {
|
||||
const moveState = this.moveState;
|
||||
const { target, bounds } = this;
|
||||
|
||||
moveState.mouseCoord = Coord.fromPointerEvent(event);
|
||||
moveState.startCoord = Coord.fromElementStyle(target);
|
||||
moveState.moveDist = new Coord(0, 0);
|
||||
moveState.totalDist = new Coord(0, 0);
|
||||
moveState.clickOffset = getClickOffset(event);
|
||||
moveState.coords = Coord.fromObject(moveState.startCoord);
|
||||
moveState.maxX =
|
||||
isFinite(bounds.left.min) && isFinite(bounds.left.max)
|
||||
? bounds.left.min + bounds.left.max
|
||||
: Infinity;
|
||||
moveState.maxY =
|
||||
isFinite(bounds.top.min) && isFinite(bounds.top.max)
|
||||
? bounds.top.min + bounds.top.max
|
||||
: Infinity;
|
||||
this.isMoving = true;
|
||||
this.reposition(true);
|
||||
this.eventBroker("movestart", event);
|
||||
}
|
||||
eventBroker(name, event) {
|
||||
this.moveState.posTop = this.posTop;
|
||||
this.moveState.posLeft = this.posLeft;
|
||||
const customEvent = new CustomEvent(name, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { ...event, ...this.moveState, element: this },
|
||||
});
|
||||
this.renderRoot.dispatchEvent(customEvent);
|
||||
const attrEvent = this[`on${name}`];
|
||||
if (attrEvent) {
|
||||
attrEvent({ ...event, ...this.moveState, me: this });
|
||||
}
|
||||
}
|
||||
unbind(event) {
|
||||
this.pointerId = null;
|
||||
document.body.removeEventListener("pointermove", (e) =>
|
||||
this.motionHandler(e),
|
||||
);
|
||||
this.moveEnd(event);
|
||||
}
|
||||
|
||||
moveEnd(event) {
|
||||
if (this.isMoving) {
|
||||
//document.body.removeEventListener('pointerup', ()=>this.unbind);
|
||||
this.isMoving = this.moveState.isMoving = false;
|
||||
this.reposition(false);
|
||||
this.eventBroker("moveend", event);
|
||||
}
|
||||
}
|
||||
|
||||
motionHandler(event) {
|
||||
//onpointermove
|
||||
event.stopPropagation();
|
||||
const newCoord = Coord.fromPointerEvent(event);
|
||||
const moveState = this.moveState;
|
||||
const { grid, bounds, shiftBehavior, boundsX, boundsY } = this;
|
||||
moveState.moveDist = Coord.fromObject({
|
||||
x: newCoord.x - moveState.mouseCoord.x,
|
||||
y: newCoord.y - moveState.mouseCoord.y,
|
||||
});
|
||||
moveState.mouseCoord = newCoord;
|
||||
|
||||
moveState.totalDist = Coord.fromObject({
|
||||
x: moveState.totalDist.x + moveState.moveDist.x,
|
||||
y: moveState.totalDist.y + moveState.moveDist.y,
|
||||
});
|
||||
moveState.coords = Coord.fromObject({
|
||||
x:
|
||||
Math.round(moveState.totalDist.x / grid) * grid +
|
||||
moveState.startCoord.x,
|
||||
y:
|
||||
Math.round(moveState.totalDist.y / grid) * grid +
|
||||
moveState.startCoord.y,
|
||||
});
|
||||
|
||||
if (
|
||||
shiftBehavior &&
|
||||
event.shiftKey &&
|
||||
boundsX.unconstrained &&
|
||||
boundsY.unconstrained
|
||||
) {
|
||||
const { x, y } = moveState.totalDist;
|
||||
if (Math.abs(x) > Math.abs(y)) {
|
||||
moveState.coords.top = moveState.startCoord.y;
|
||||
} else {
|
||||
moveState.coords.left = moveState.startCoord.x;
|
||||
}
|
||||
} else {
|
||||
moveState.coords.y = Math.min(
|
||||
Math.max(bounds.top.min, moveState.coords.top),
|
||||
bounds.top.max,
|
||||
);
|
||||
moveState.coords.x = Math.min(
|
||||
Math.max(bounds.left.min, moveState.coords.left),
|
||||
bounds.left.max,
|
||||
);
|
||||
}
|
||||
if (isFinite(moveState.maxX)) {
|
||||
moveState.pctX =
|
||||
Math.max(bounds.left.min, moveState.coords.left) / moveState.maxX;
|
||||
}
|
||||
if (isFinite(moveState.maxY)) {
|
||||
moveState.pctY =
|
||||
Math.max(bounds.top.min, moveState.coords.top) / moveState.maxY;
|
||||
}
|
||||
this.reposition(moveState.coords);
|
||||
this.eventBroker("move", event);
|
||||
}
|
||||
pointerdown(event) {
|
||||
document.body.setPointerCapture(event.pointerId);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.pointerId !== undefined) {
|
||||
this.pointerId = event.pointerId;
|
||||
}
|
||||
|
||||
if (!this.listening) {
|
||||
document.body.addEventListener(
|
||||
"pointerup",
|
||||
(event) => {
|
||||
if (this.isMoving) {
|
||||
this.unbind(event);
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
document.body.addEventListener(
|
||||
"pointermove",
|
||||
(event) => {
|
||||
if (
|
||||
this.pointerId !== undefined &&
|
||||
event.pointerId === this.pointerId
|
||||
) {
|
||||
this.motionHandler(event);
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
this.listening = true;
|
||||
this.moveInit(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.customElements.get("lit-movable")) {
|
||||
window.customElements.define("lit-movable", LitMovable);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
export const loadHaServiceControl = async (): Promise<void> => {
|
||||
if (customElements.get("ha-service-control")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load in ha-service-control from developer-tools-service
|
||||
const ppResolver = document.createElement("partial-panel-resolver");
|
||||
const routes = (ppResolver as any).getRoutes([
|
||||
{
|
||||
component_name: "developer-tools",
|
||||
url_path: "a",
|
||||
},
|
||||
]);
|
||||
await routes?.routes?.a?.load?.();
|
||||
const devToolsRouter = document.createElement("developer-tools-router");
|
||||
const devToolsRoutes = (devToolsRouter as any)?.routerOptions?.routes;
|
||||
if (devToolsRoutes?.service) {
|
||||
await devToolsRoutes?.service?.load?.();
|
||||
}
|
||||
if (devToolsRoutes?.action) {
|
||||
await devToolsRoutes?.action?.load?.();
|
||||
}
|
||||
};
|
||||
466
custom_components/kobrax_lan/frontend_panel/src/types.ts
Normal file
466
custom_components/kobrax_lan/frontend_panel/src/types.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
import {
|
||||
Connection,
|
||||
HassEntityAttributeBase,
|
||||
HassServices,
|
||||
MessageBase,
|
||||
HassEntities as _HassEntities,
|
||||
HassEntity as _HassEntity,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { TemplateResult, nothing } from "lit";
|
||||
import { ColorPicker as _ColorPicker } from "./lib/colorpicker/ColorPicker.js";
|
||||
|
||||
export type LitTemplateResult = typeof nothing | TemplateResult;
|
||||
|
||||
export type ColorPicker = _ColorPicker;
|
||||
|
||||
export interface Dictionary<TValue> {
|
||||
[id: string]: TValue;
|
||||
}
|
||||
|
||||
export interface ServiceCallRequest {
|
||||
domain: string;
|
||||
action: string;
|
||||
service: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
serviceData?: Record<string, any>;
|
||||
target?: {
|
||||
entity_id?: string | string[];
|
||||
device_id?: string | string[];
|
||||
area_id?: string | string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface HassEmptyEntity {
|
||||
state: string;
|
||||
attributes: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HassDevice {
|
||||
id: string;
|
||||
config_entries: string[];
|
||||
name: string;
|
||||
model?: string;
|
||||
sw_version?: string;
|
||||
primary_config_entry: string;
|
||||
manufacturer: string | null;
|
||||
serial_number: string | undefined;
|
||||
connections: string[][];
|
||||
}
|
||||
|
||||
export interface HassDeviceList {
|
||||
[id: string]: HassDevice;
|
||||
}
|
||||
|
||||
export type HassEntities = _HassEntities;
|
||||
export type HassEntity = _HassEntity;
|
||||
|
||||
export type HassEntityInfo = {
|
||||
entity_id: string;
|
||||
device_id?: string;
|
||||
labels?: string[];
|
||||
translation_key?: string;
|
||||
platform?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type HassEntityInfos = {
|
||||
[entity_id: string]: HassEntityInfo;
|
||||
};
|
||||
|
||||
export interface HomeAssistant {
|
||||
connection: Connection;
|
||||
language: string;
|
||||
panels: {
|
||||
[name: string]: {
|
||||
component_name: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
config: { [key: string]: any } | null;
|
||||
icon: string | null;
|
||||
title: string | null;
|
||||
url_path: string;
|
||||
};
|
||||
};
|
||||
devices: HassDeviceList;
|
||||
entities: HassEntityInfos;
|
||||
states: HassEntities;
|
||||
services: HassServices;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
localize: (key: string, ...args: any[]) => string;
|
||||
translationMetadata: {
|
||||
fragments: string[];
|
||||
translations: {
|
||||
[lang: string]: {
|
||||
nativeName: string;
|
||||
isRTL: boolean;
|
||||
fingerprints: { [fragment: string]: string };
|
||||
};
|
||||
};
|
||||
};
|
||||
callApi: <T>(
|
||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||
path: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parameters?: { [key: string]: any },
|
||||
) => Promise<T>;
|
||||
callService: (
|
||||
domain: ServiceCallRequest["domain"],
|
||||
action: ServiceCallRequest["action"],
|
||||
serviceData?: ServiceCallRequest["serviceData"],
|
||||
target?: ServiceCallRequest["target"],
|
||||
) => Promise<void>;
|
||||
callWS: <T>(msg: MessageBase) => Promise<T>;
|
||||
}
|
||||
|
||||
export interface HassRoute {
|
||||
prefix: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface HaTextField {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type HaFormData = string | number | boolean | string[];
|
||||
|
||||
export interface HaFormBaseSchema {
|
||||
name: string;
|
||||
default?: HaFormData;
|
||||
required?: boolean;
|
||||
description?: {
|
||||
suffix?: string;
|
||||
suggested_value?: HaFormData;
|
||||
};
|
||||
context?: Record<string, string>;
|
||||
type?: never;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
selector?: any;
|
||||
}
|
||||
|
||||
export interface AnycubicFileLocal {
|
||||
name: string;
|
||||
size_mb: number;
|
||||
}
|
||||
|
||||
export interface AnycubicFileCloud extends AnycubicFileLocal {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export enum CalculatedTimeType {
|
||||
ETA = "ETA",
|
||||
Elapsed = "Elapsed",
|
||||
Remaining = "Remaining",
|
||||
}
|
||||
|
||||
export enum TemperatureUnit {
|
||||
F = "F",
|
||||
C = "C",
|
||||
}
|
||||
|
||||
export enum StatTypeGeneral {
|
||||
Status = "Status",
|
||||
PrinterOnline = "Online",
|
||||
Availability = "Availability",
|
||||
ProjectName = "Project",
|
||||
CurrentLayer = "Layer",
|
||||
}
|
||||
|
||||
export enum StatTypeFDM {
|
||||
HotendCurrent = "Hotend",
|
||||
BedCurrent = "Bed",
|
||||
HotendTarget = "T Hotend",
|
||||
BedTarget = "T Bed",
|
||||
DryingStatus = "Dry Status",
|
||||
DryingTime = "Dry Time",
|
||||
SpeedMode = "Speed Mode",
|
||||
FanSpeed = "Fan Speed",
|
||||
}
|
||||
|
||||
export enum StatTypeACE {
|
||||
DryingStatus = "Dry Status",
|
||||
DryingTime = "Dry Time",
|
||||
}
|
||||
|
||||
export enum StatTypeLCD {
|
||||
OnTime = "On Time",
|
||||
OffTime = "Off Time",
|
||||
BottomTime = "Bottom Time",
|
||||
ModelHeight = "Model Height",
|
||||
BottomLayers = "Bottom Layers",
|
||||
ZUpHeight = "Z Up Height",
|
||||
ZUpSpeed = "Z Up Speed",
|
||||
ZDownSpeed = "Z Down Speed",
|
||||
}
|
||||
|
||||
export const PrinterCardStatType = {
|
||||
...CalculatedTimeType,
|
||||
...StatTypeGeneral,
|
||||
...StatTypeFDM,
|
||||
...StatTypeACE,
|
||||
...StatTypeLCD,
|
||||
};
|
||||
export type PrinterCardStatType =
|
||||
| CalculatedTimeType
|
||||
| StatTypeGeneral
|
||||
| StatTypeFDM
|
||||
| StatTypeACE
|
||||
| StatTypeLCD;
|
||||
|
||||
export interface AnimatedPrinterBasicDimension {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterXYDimension {
|
||||
X: number;
|
||||
Y: number;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterLTDimension
|
||||
extends AnimatedPrinterBasicDimension {
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterLTWidth {
|
||||
width: number;
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterBuildPlateDimension {
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
verticalOffset: number;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterAxisConfig
|
||||
extends AnimatedPrinterBasicDimension {
|
||||
stepper: boolean;
|
||||
offsetLeft: number;
|
||||
extruder: AnimatedPrinterBasicDimension;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterConfig {
|
||||
top: AnimatedPrinterBasicDimension;
|
||||
bottom: AnimatedPrinterBasicDimension;
|
||||
left: AnimatedPrinterBasicDimension;
|
||||
right: AnimatedPrinterBasicDimension;
|
||||
buildplate: AnimatedPrinterBuildPlateDimension;
|
||||
xAxis: AnimatedPrinterAxisConfig;
|
||||
}
|
||||
|
||||
export interface AnimatedPrinterDimensions {
|
||||
Scalable: AnimatedPrinterBasicDimension;
|
||||
Frame: AnimatedPrinterBasicDimension;
|
||||
Hole: AnimatedPrinterLTDimension;
|
||||
BuildArea: AnimatedPrinterLTDimension;
|
||||
BuildPlate: AnimatedPrinterLTWidth;
|
||||
XAxis: AnimatedPrinterLTDimension;
|
||||
Track: AnimatedPrinterBasicDimension;
|
||||
Basis: AnimatedPrinterXYDimension;
|
||||
Gantry: AnimatedPrinterLTDimension;
|
||||
Nozzle: AnimatedPrinterLTDimension;
|
||||
GantryMaxLeft: number;
|
||||
}
|
||||
|
||||
export interface AnycubicSpoolInfo {
|
||||
material_type: string;
|
||||
color: number[];
|
||||
status: number;
|
||||
spool_loaded: boolean;
|
||||
}
|
||||
|
||||
export interface AnycubicSpeedMode {
|
||||
description: string;
|
||||
mode: number;
|
||||
}
|
||||
|
||||
export interface SelectDropdownProps {
|
||||
[key: string | number]: string;
|
||||
}
|
||||
|
||||
export interface AnycubicSpeedModes {
|
||||
[key: number]: string;
|
||||
}
|
||||
|
||||
export interface AnycubicCardConfig {
|
||||
printer_id?: string;
|
||||
vertical?: boolean;
|
||||
round?: boolean;
|
||||
use_24hr?: boolean;
|
||||
temperatureUnit?: TemperatureUnit;
|
||||
lightEntityId?: string;
|
||||
powerEntityId?: string;
|
||||
cameraEntityId?: string;
|
||||
monitoredStats?: PrinterCardStatType[];
|
||||
scaleFactor?: number;
|
||||
slotColors?: string[];
|
||||
showSettingsButton?: boolean;
|
||||
alwaysShow?: boolean;
|
||||
}
|
||||
|
||||
export enum AnycubicMaterialType {
|
||||
PLA = "PLA",
|
||||
PETG = "PETG",
|
||||
ABS = "ABS",
|
||||
PACF = "PACF",
|
||||
PC = "PC",
|
||||
ASA = "ASA",
|
||||
HIPS = "HIPS",
|
||||
PA = "PA",
|
||||
PLA_SE = "PLA_SE",
|
||||
}
|
||||
|
||||
export enum AnycubicPrintOptionConfirmationType {
|
||||
PAUSE = "pause",
|
||||
RESUME = "resume",
|
||||
CANCEL = "cancel",
|
||||
}
|
||||
|
||||
export interface AnycubicFileListEntity extends HassEntity {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
file_info?: AnycubicFileLocal[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnycubicCloudFileListEntity extends HassEntity {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
file_info?: AnycubicFileCloud[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnycubicTargetTempEntity extends HassEntity {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
limit_min: number;
|
||||
limit_max: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnycubicSpeedModeEntity extends HassEntity {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
available_modes: AnycubicSpeedMode[];
|
||||
print_speed_mode_code: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnycubicDryingPresetEntity extends HassEntity {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
temperature?: number;
|
||||
duration?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnycubicSpoolInfoEntity extends HassEntity {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
spool_info: AnycubicSpoolInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface TranslationDict {
|
||||
[id: string]: string;
|
||||
}
|
||||
|
||||
export interface HassPanel {
|
||||
config: AnycubicCardConfig;
|
||||
}
|
||||
|
||||
export interface PageChangeDetail {
|
||||
item: Element;
|
||||
}
|
||||
|
||||
export interface ModalEventBase {
|
||||
modalOpen: boolean;
|
||||
}
|
||||
|
||||
export interface ModalEventDrying extends ModalEventBase {
|
||||
box_id: number | string;
|
||||
}
|
||||
|
||||
export interface ModalEventSpool extends ModalEventBase {
|
||||
box_id: number | string;
|
||||
spool_index: number | string;
|
||||
material_type?: string;
|
||||
color: number[] | string | undefined;
|
||||
}
|
||||
|
||||
export interface FormChangeDetail {
|
||||
value: object;
|
||||
}
|
||||
|
||||
export interface TextfieldChangeDetail<TValue> {
|
||||
value: TValue;
|
||||
}
|
||||
|
||||
export interface DropdownEvent<KValue, TValue> {
|
||||
key: KValue;
|
||||
value: TValue;
|
||||
}
|
||||
|
||||
export interface ColourPickEvent {
|
||||
color?: {
|
||||
rgb: number[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface HassServiceError {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface HassProgressButton {
|
||||
actionSuccess: () => void;
|
||||
actionError: () => void;
|
||||
}
|
||||
|
||||
export interface CustomCardEntry {
|
||||
type: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
preview?: boolean;
|
||||
documentationURL?: string;
|
||||
}
|
||||
|
||||
export interface CustomCardsWindow {
|
||||
customCards?: CustomCardEntry[];
|
||||
}
|
||||
|
||||
export interface AnycubicLitNode {
|
||||
route: HassRoute;
|
||||
}
|
||||
|
||||
export interface DomClickEvent<T extends EventTarget> extends Event {
|
||||
currentTarget: T;
|
||||
}
|
||||
|
||||
export interface EvtTargPrinterDevId extends EventTarget {
|
||||
printer_id: string;
|
||||
}
|
||||
|
||||
export interface EvtTargConfirmationMode extends EventTarget {
|
||||
confirmation_type: AnycubicPrintOptionConfirmationType;
|
||||
}
|
||||
|
||||
export interface EvtTargFileInfo extends EventTarget {
|
||||
file_info: AnycubicFileCloud | AnycubicFileLocal;
|
||||
}
|
||||
|
||||
export interface EvtTargItemKey extends EventTarget {
|
||||
item_key: string | number;
|
||||
}
|
||||
|
||||
export interface EvtTargDirection extends EventTarget {
|
||||
direction: number;
|
||||
}
|
||||
|
||||
export interface EvtTargSpoolEdit extends EventTarget {
|
||||
index: number;
|
||||
material_type: string;
|
||||
color: number[];
|
||||
}
|
||||
|
||||
export interface EvtTargColourPreset extends EventTarget {
|
||||
preset: string;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import { getPrinterEntities } from "../../helpers";
|
||||
import {
|
||||
HassDevice,
|
||||
HassDeviceList,
|
||||
HassEntityInfos,
|
||||
HassPanel,
|
||||
HassRoute,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
} from "../../types";
|
||||
|
||||
@customElement("anycubic-view-debug")
|
||||
export class AnycubicViewDebug extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property()
|
||||
public route!: HassRoute;
|
||||
|
||||
@property()
|
||||
public panel!: HassPanel;
|
||||
|
||||
@property()
|
||||
public printers?: HassDeviceList;
|
||||
|
||||
@property({ attribute: "selected-printer-id" })
|
||||
public selectedPrinterID: string | undefined;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@state()
|
||||
private printerEntities: HassEntityInfos;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (!changedProperties.has("selectedPrinterID")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.printerEntities = getPrinterEntities(
|
||||
this.hass,
|
||||
this.selectedPrinterID,
|
||||
);
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<debug-data elevation="2">
|
||||
<p>There are ${Object.keys(this.hass.states).length} entities.</p>
|
||||
<p>The screen is${this.narrow ? "" : " not"} narrow.</p>
|
||||
Configured panel config
|
||||
<pre>${JSON.stringify(this.panel, undefined, 2)}</pre>
|
||||
Current route
|
||||
<pre>${JSON.stringify(this.route, undefined, 2)}</pre>
|
||||
Printers
|
||||
<pre>${JSON.stringify(this.printers, undefined, 2)}</pre>
|
||||
Printer Entities
|
||||
<pre>${JSON.stringify(this.printerEntities, undefined, 2)}</pre>
|
||||
Selected Printer
|
||||
<pre>${JSON.stringify(this.selectedPrinterDevice, undefined, 2)}</pre>
|
||||
</debug-data>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
}
|
||||
debug-data {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { CSSResult, css } from "lit";
|
||||
|
||||
export const commonFilesStyle: CSSResult = css`
|
||||
:host {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.files-card {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.files-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
min-height: 20px;
|
||||
min-width: 250px;
|
||||
border: 2px solid #ccc3;
|
||||
border-radius: 16px;
|
||||
padding: 16px 32px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
margin: 6px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
margin: 6px;
|
||||
word-wrap: break-word;
|
||||
max-width: calc(100% - 58px);
|
||||
}
|
||||
|
||||
.file-info:hover {
|
||||
background-color: #ccc3;
|
||||
border-color: #ccc9;
|
||||
}
|
||||
|
||||
.file-refresh-button {
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.file-refresh-icon {
|
||||
--mdc-icon-size: 50px;
|
||||
}
|
||||
|
||||
.file-delete-button {
|
||||
padding: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.file-delete-icon {
|
||||
}
|
||||
|
||||
.no-mqtt-msg {
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
:host {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.files-card {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
padding: 6px 6px;
|
||||
margin: 6px 0px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,170 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
import { commonFilesStyle } from "./styles";
|
||||
import { localize } from "../../../localize/localize";
|
||||
import {
|
||||
getPrinterEntities,
|
||||
getPrinterEntityIdPart,
|
||||
getPrinterSupportsMQTT,
|
||||
} from "../../helpers";
|
||||
import {
|
||||
AnycubicFileLocal,
|
||||
DomClickEvent,
|
||||
EvtTargFileInfo,
|
||||
HassDevice,
|
||||
HassEntityInfo,
|
||||
HassEntityInfos,
|
||||
HassPanel,
|
||||
HassRoute,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
} from "../../types";
|
||||
|
||||
export class AnycubicViewFilesBase extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property()
|
||||
public route!: HassRoute;
|
||||
|
||||
@property()
|
||||
public panel!: HassPanel;
|
||||
|
||||
@property({ attribute: "selected-printer-id" })
|
||||
public selectedPrinterID: string | undefined;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@state()
|
||||
protected printerEntities: HassEntityInfos;
|
||||
|
||||
@state()
|
||||
private printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
protected _fileArray: AnycubicFileLocal[] | undefined;
|
||||
|
||||
@state()
|
||||
protected _listRefreshEntity: HassEntityInfo | undefined;
|
||||
|
||||
@state()
|
||||
private _isRefreshing: boolean = false;
|
||||
|
||||
@state()
|
||||
protected _isDeleting: boolean;
|
||||
|
||||
@state()
|
||||
private _noMqttMessage: string;
|
||||
|
||||
@state()
|
||||
private _supportsMQTT: boolean = false;
|
||||
|
||||
@state()
|
||||
protected _httpResponse: boolean = false;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._noMqttMessage = localize(
|
||||
"common.messages.mqtt_unsupported",
|
||||
this.language,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("selectedPrinterID")
|
||||
) {
|
||||
this.printerEntities = getPrinterEntities(
|
||||
this.hass,
|
||||
this.selectedPrinterID,
|
||||
);
|
||||
this.printerEntityIdPart = getPrinterEntityIdPart(this.printerEntities);
|
||||
this._supportsMQTT = getPrinterSupportsMQTT(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<div class="files-card" elevation="2">
|
||||
<button
|
||||
.disabled=${(!this._httpResponse && !this._supportsMQTT) || this._isRefreshing}
|
||||
class="file-refresh-button"
|
||||
@click=${this.refreshList}
|
||||
>
|
||||
<ha-icon
|
||||
class="file-refresh-icon"
|
||||
icon="mdi:refresh"
|
||||
>
|
||||
</ha-icon>
|
||||
</button>
|
||||
${
|
||||
!this._httpResponse && !this._supportsMQTT
|
||||
? html` <div class="no-mqtt-msg">${this._noMqttMessage}</div> `
|
||||
: nothing
|
||||
}
|
||||
<ul class="files-container">
|
||||
${
|
||||
this._fileArray
|
||||
? this._fileArray.map(
|
||||
(fileInfo) => html`
|
||||
<li class="file-info">
|
||||
<div class="file-name">${fileInfo.name}</div>
|
||||
<button
|
||||
class="file-delete-button"
|
||||
.disabled=${this._isDeleting}
|
||||
.file_info=${fileInfo}
|
||||
@click=${this.deleteFile}
|
||||
>
|
||||
<ha-icon
|
||||
class="file-delete-icon"
|
||||
icon="mdi:delete"
|
||||
></ha-icon>
|
||||
</button>
|
||||
</li>
|
||||
`,
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
refreshList = (): void => {
|
||||
if (this._listRefreshEntity) {
|
||||
this._isRefreshing = true;
|
||||
this.hass
|
||||
.callService("button", "press", {
|
||||
entity_id: this._listRefreshEntity.entity_id,
|
||||
})
|
||||
.then(() => {
|
||||
this._isRefreshing = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._isRefreshing = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-empty-function
|
||||
deleteFile = (_ev: DomClickEvent<EvtTargFileInfo>): void => {};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
${commonFilesStyle}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { AnycubicViewFilesBase } from "./view-files_base";
|
||||
import { platform } from "../../const";
|
||||
import {
|
||||
getEntityState,
|
||||
getFileListCloudFilesEntity,
|
||||
getFileListCloudRefreshEntity,
|
||||
} from "../../helpers";
|
||||
import {
|
||||
AnycubicCloudFileListEntity,
|
||||
AnycubicFileCloud,
|
||||
DomClickEvent,
|
||||
EvtTargFileInfo,
|
||||
} from "../../types";
|
||||
|
||||
@customElement("anycubic-view-files_cloud")
|
||||
export class AnycubicViewFilesCloud extends AnycubicViewFilesBase {
|
||||
@state()
|
||||
protected _fileArray: AnycubicFileCloud[] | undefined;
|
||||
|
||||
@state()
|
||||
protected _httpResponse: boolean = true;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("selectedPrinterID")
|
||||
) {
|
||||
const fileListState: AnycubicCloudFileListEntity | undefined =
|
||||
getEntityState(
|
||||
this.hass,
|
||||
getFileListCloudFilesEntity(this.printerEntities),
|
||||
);
|
||||
this._fileArray = fileListState
|
||||
? fileListState.attributes.file_info
|
||||
: undefined;
|
||||
this._listRefreshEntity = getFileListCloudRefreshEntity(
|
||||
this.printerEntities,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteFile = (ev: DomClickEvent<EvtTargFileInfo>): void => {
|
||||
const fileInfo: AnycubicFileCloud = ev.currentTarget
|
||||
.file_info as AnycubicFileCloud;
|
||||
if (this.selectedPrinterDevice && fileInfo.id) {
|
||||
this._isDeleting = true;
|
||||
this.hass
|
||||
.callService(platform, "delete_file_cloud", {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
file_id: fileInfo.id,
|
||||
})
|
||||
.then(() => {
|
||||
this._isDeleting = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._isDeleting = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AnycubicViewFilesBase } from "./view-files_base";
|
||||
import { platform } from "../../const";
|
||||
import {
|
||||
getEntityState,
|
||||
getFileListLocalFilesEntity,
|
||||
getFileListLocalRefreshEntity,
|
||||
} from "../../helpers";
|
||||
import {
|
||||
AnycubicFileListEntity,
|
||||
AnycubicFileLocal,
|
||||
DomClickEvent,
|
||||
EvtTargFileInfo,
|
||||
} from "../../types";
|
||||
|
||||
@customElement("anycubic-view-files_local")
|
||||
export class AnycubicViewFilesLocal extends AnycubicViewFilesBase {
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("selectedPrinterID")
|
||||
) {
|
||||
const fileListState: AnycubicFileListEntity | undefined = getEntityState(
|
||||
this.hass,
|
||||
getFileListLocalFilesEntity(this.printerEntities),
|
||||
);
|
||||
this._fileArray = fileListState
|
||||
? fileListState.attributes.file_info
|
||||
: undefined;
|
||||
this._listRefreshEntity = getFileListLocalRefreshEntity(
|
||||
this.printerEntities,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteFile = (ev: DomClickEvent<EvtTargFileInfo>): void => {
|
||||
const fileInfo: AnycubicFileLocal = ev.currentTarget
|
||||
.file_info as AnycubicFileLocal;
|
||||
if (this.selectedPrinterDevice && fileInfo.name) {
|
||||
this._isDeleting = true;
|
||||
this.hass
|
||||
.callService(platform, "delete_file_local", {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
filename: fileInfo.name,
|
||||
})
|
||||
.then(() => {
|
||||
this._isDeleting = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._isDeleting = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { PropertyValues } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { AnycubicViewFilesBase } from "./view-files_base";
|
||||
import { platform } from "../../const";
|
||||
import {
|
||||
getEntityState,
|
||||
getFileListUdiskFilesEntity,
|
||||
getFileListUdiskRefreshEntity,
|
||||
} from "../../helpers";
|
||||
import {
|
||||
AnycubicFileListEntity,
|
||||
AnycubicFileLocal,
|
||||
DomClickEvent,
|
||||
EvtTargFileInfo,
|
||||
} from "../../types";
|
||||
|
||||
@customElement("anycubic-view-files_udisk")
|
||||
export class AnycubicViewFilesUdisk extends AnycubicViewFilesBase {
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("selectedPrinterID")
|
||||
) {
|
||||
const fileListState: AnycubicFileListEntity | undefined = getEntityState(
|
||||
this.hass,
|
||||
getFileListUdiskFilesEntity(this.printerEntities),
|
||||
);
|
||||
this._fileArray = fileListState
|
||||
? fileListState.attributes.file_info
|
||||
: undefined;
|
||||
this._listRefreshEntity = getFileListUdiskRefreshEntity(
|
||||
this.printerEntities,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteFile = (ev: DomClickEvent<EvtTargFileInfo>): void => {
|
||||
const fileInfo: AnycubicFileLocal = ev.currentTarget
|
||||
.file_info as AnycubicFileLocal;
|
||||
if (this.selectedPrinterDevice && fileInfo.name) {
|
||||
this._isDeleting = true;
|
||||
this.hass
|
||||
.callService(platform, "delete_file_udisk", {
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
filename: fileInfo.name,
|
||||
})
|
||||
.then(() => {
|
||||
this._isDeleting = false;
|
||||
})
|
||||
.catch((_e: unknown) => {
|
||||
this._isDeleting = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import { localize } from "../../../localize/localize";
|
||||
|
||||
import {
|
||||
getPanelACEMonitoredStats,
|
||||
getPanelBasicMonitoredStats,
|
||||
getPanelFDMMonitoredStats,
|
||||
getPrinterBinarySensorState,
|
||||
getPrinterEntities,
|
||||
getPrinterEntityIdPart,
|
||||
getPrinterID,
|
||||
getPrinterMAC,
|
||||
getPrinterSensorStateFloat,
|
||||
getPrinterSensorStateString,
|
||||
getPrinterUpdateEntityState,
|
||||
isFDMPrinter,
|
||||
} from "../../helpers";
|
||||
import {
|
||||
HassDevice,
|
||||
HassEntityInfos,
|
||||
HassPanel,
|
||||
HassRoute,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
PrinterCardStatType,
|
||||
TranslationDict,
|
||||
} from "../../types";
|
||||
|
||||
import "../../components/printer_card/card/card.ts";
|
||||
|
||||
const monitoredStatsACE: PrinterCardStatType[] = getPanelACEMonitoredStats();
|
||||
const monitoredStatsBasic: PrinterCardStatType[] =
|
||||
getPanelBasicMonitoredStats();
|
||||
const monitoredStatsFDM: PrinterCardStatType[] = getPanelFDMMonitoredStats();
|
||||
|
||||
const infoFields: string[] = [
|
||||
"printer_name",
|
||||
"printer_id",
|
||||
"printer_mac",
|
||||
"printer_model",
|
||||
"printer_fw_version",
|
||||
"printer_fw_update_available",
|
||||
"printer_online",
|
||||
"printer_available",
|
||||
"curr_nozzle_temp",
|
||||
"curr_hotbed_temp",
|
||||
"target_nozzle_temp",
|
||||
"target_hotbed_temp",
|
||||
"job_state",
|
||||
"job_progress",
|
||||
"ace_fw_version",
|
||||
"ace_fw_update_available",
|
||||
"drying_active",
|
||||
"drying_progress",
|
||||
];
|
||||
|
||||
@customElement("anycubic-view-main")
|
||||
export class AnycubicViewMain extends LitElement {
|
||||
@property()
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property()
|
||||
public route!: HassRoute;
|
||||
|
||||
@property()
|
||||
public panel!: HassPanel;
|
||||
|
||||
@property({ attribute: "selected-printer-id" })
|
||||
public selectedPrinterID: string | undefined;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@state()
|
||||
private printerEntities: HassEntityInfos;
|
||||
|
||||
@state()
|
||||
private printerEntityIdPart: string | undefined;
|
||||
|
||||
@state()
|
||||
private printerID: string | undefined;
|
||||
|
||||
@state()
|
||||
private printerMAC: string | null;
|
||||
|
||||
@state()
|
||||
private printerStateFwUpdateAvailable: string | undefined;
|
||||
|
||||
@state()
|
||||
private printerStateAvailable: string | boolean | undefined;
|
||||
|
||||
@state()
|
||||
private printerStateOnline: string | boolean | undefined;
|
||||
|
||||
@state()
|
||||
private printerStateCurrNozzleTemp: number | undefined;
|
||||
|
||||
@state()
|
||||
private printerStateCurrHotbedTemp: number | undefined;
|
||||
|
||||
@state()
|
||||
private printerStateTargetNozzleTemp: number | undefined;
|
||||
|
||||
@state()
|
||||
private printerStateTargetHotbedTemp: number | undefined;
|
||||
|
||||
@state()
|
||||
private jobStateProgress: string | undefined;
|
||||
|
||||
@state()
|
||||
private jobStatePrintState: string | undefined;
|
||||
|
||||
@state()
|
||||
private aceStateFwUpdateAvailable: string | boolean | undefined;
|
||||
|
||||
@state()
|
||||
private aceStateDryingActive: string | boolean | undefined;
|
||||
|
||||
@state()
|
||||
private aceStateDryingRemaining: number | undefined;
|
||||
|
||||
@state()
|
||||
private aceStateDryingTotal: number | undefined;
|
||||
|
||||
@state()
|
||||
private aceDryingProgress: string | undefined;
|
||||
|
||||
@state()
|
||||
private isFDM: boolean = false;
|
||||
|
||||
@state()
|
||||
private monitoredStats: PrinterCardStatType[] = monitoredStatsBasic;
|
||||
|
||||
@state()
|
||||
private _statTranslations: TranslationDict;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._statTranslations = infoFields.reduce((fConf, fieldKey) => {
|
||||
fConf[fieldKey] = localize(
|
||||
`panels.main.cards.main.fields.${fieldKey}`,
|
||||
this.language,
|
||||
);
|
||||
return fConf;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (changedProperties.has("selectedPrinterDevice")) {
|
||||
this.printerID = getPrinterID(this.selectedPrinterDevice);
|
||||
this.printerMAC = getPrinterMAC(this.selectedPrinterDevice);
|
||||
}
|
||||
|
||||
if (changedProperties.has("selectedPrinterID")) {
|
||||
this.printerEntities = getPrinterEntities(
|
||||
this.hass,
|
||||
this.selectedPrinterID,
|
||||
);
|
||||
this.printerEntityIdPart = getPrinterEntityIdPart(this.printerEntities);
|
||||
}
|
||||
|
||||
if (
|
||||
changedProperties.has("hass") ||
|
||||
changedProperties.has("selectedPrinterID")
|
||||
) {
|
||||
this.isFDM = isFDMPrinter(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
);
|
||||
this.printerStateFwUpdateAvailable = getPrinterUpdateEntityState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"printer_firmware",
|
||||
);
|
||||
this.printerStateAvailable = getPrinterBinarySensorState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"is_available",
|
||||
"Available",
|
||||
"Busy",
|
||||
);
|
||||
this.printerStateOnline = getPrinterBinarySensorState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"printer_online",
|
||||
"Online",
|
||||
"Offline",
|
||||
);
|
||||
this.printerStateCurrNozzleTemp = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"nozzle_temperature",
|
||||
);
|
||||
this.printerStateCurrHotbedTemp = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"hotbed_temperature",
|
||||
);
|
||||
this.printerStateTargetNozzleTemp = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"target_nozzle_temperature",
|
||||
);
|
||||
this.printerStateTargetHotbedTemp = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"target_hotbed_temperature",
|
||||
);
|
||||
const projProgress = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_progress",
|
||||
);
|
||||
this.jobStateProgress =
|
||||
typeof projProgress !== "undefined" ? `${projProgress}%` : "0%";
|
||||
this.jobStatePrintState = getPrinterSensorStateString(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"job_state",
|
||||
true,
|
||||
);
|
||||
this.aceStateFwUpdateAvailable = getPrinterUpdateEntityState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"ace_firmware",
|
||||
);
|
||||
this.aceStateDryingActive = getPrinterBinarySensorState(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"drying_active",
|
||||
"Drying",
|
||||
"Not Drying",
|
||||
);
|
||||
this.aceStateDryingRemaining = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"drying_remaining_time",
|
||||
);
|
||||
this.aceStateDryingTotal = getPrinterSensorStateFloat(
|
||||
this.hass,
|
||||
this.printerEntities,
|
||||
this.printerEntityIdPart,
|
||||
"drying_total_duration",
|
||||
);
|
||||
this.aceDryingProgress =
|
||||
typeof this.aceStateDryingRemaining !== "undefined" &&
|
||||
typeof this.aceStateDryingTotal !== "undefined"
|
||||
? String(
|
||||
(this.aceStateDryingTotal > 0
|
||||
? Math.round(
|
||||
(1 -
|
||||
this.aceStateDryingRemaining / this.aceStateDryingTotal) *
|
||||
10000,
|
||||
) / 100
|
||||
: 0
|
||||
).toFixed(2),
|
||||
) + "%"
|
||||
: undefined;
|
||||
if (this.aceStateFwUpdateAvailable) {
|
||||
this.monitoredStats = monitoredStatsACE;
|
||||
} else if (this.isFDM) {
|
||||
this.monitoredStats = monitoredStatsFDM;
|
||||
} else {
|
||||
this.monitoredStats = monitoredStatsBasic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _renderInfoRow(
|
||||
fieldKey: string,
|
||||
rowData: string | number | boolean | undefined | null,
|
||||
): LitTemplateResult {
|
||||
return html`
|
||||
<div class="info-row">
|
||||
<span class="info-heading"> ${this._statTranslations[fieldKey]}:</span>
|
||||
<span class="info-detail">${rowData}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderOptionalInfoRow(
|
||||
fieldKey: string,
|
||||
rowData: string | number | boolean | undefined | null,
|
||||
): LitTemplateResult | null {
|
||||
return typeof rowData !== "undefined"
|
||||
? this._renderInfoRow(fieldKey, rowData)
|
||||
: null;
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<printer-card elevation="2">
|
||||
<anycubic-printercard-card
|
||||
.hass=${this.hass}
|
||||
.language=${this.language}
|
||||
.selectedPrinterID=${this.selectedPrinterID}
|
||||
.selectedPrinterDevice=${this.selectedPrinterDevice}
|
||||
.vertical=${this.panel.config.vertical ?? false}
|
||||
.round=${this.panel.config.round ?? false}
|
||||
.use_24hr=${this.panel.config.use_24hr ?? true}
|
||||
.temperatureUnit=${this.panel.config.temperatureUnit}
|
||||
.lightEntityId=${this.panel.config.lightEntityId}
|
||||
.powerEntityId=${this.panel.config.powerEntityId}
|
||||
.cameraEntityId=${this.panel.config.cameraEntityId}
|
||||
.monitoredStats=${this.panel.config.monitoredStats ??
|
||||
this.monitoredStats}
|
||||
.scaleFactor=${this.panel.config.scaleFactor}
|
||||
.slotColors=${this.panel.config.slotColors}
|
||||
.showSettingsButton=${this.panel.config.showSettingsButton ?? true}
|
||||
.alwaysShow=${this.panel.config.alwaysShow}
|
||||
></anycubic-printercard-card>
|
||||
<div class="ac-extra-printer-info">
|
||||
${this._renderInfoRow(
|
||||
"printer_name",
|
||||
this.selectedPrinterDevice ? this.selectedPrinterDevice.name : null,
|
||||
)}
|
||||
${this._renderInfoRow("printer_id", this.printerID)}
|
||||
${this._renderInfoRow("printer_mac", this.printerMAC)}
|
||||
${this._renderInfoRow(
|
||||
"printer_model",
|
||||
this.selectedPrinterDevice
|
||||
? this.selectedPrinterDevice.model
|
||||
: null,
|
||||
)}
|
||||
${this._renderInfoRow(
|
||||
"printer_fw_version",
|
||||
this.selectedPrinterDevice
|
||||
? this.selectedPrinterDevice.sw_version
|
||||
: null,
|
||||
)}
|
||||
${this._renderInfoRow(
|
||||
"printer_fw_update_available",
|
||||
this.printerStateFwUpdateAvailable,
|
||||
)}
|
||||
${this._renderInfoRow("printer_online", this.printerStateOnline)}
|
||||
${this._renderInfoRow(
|
||||
"printer_available",
|
||||
this.printerStateAvailable,
|
||||
)}
|
||||
${this.isFDM
|
||||
? html`
|
||||
${this._renderInfoRow(
|
||||
"curr_nozzle_temp",
|
||||
this.printerStateCurrNozzleTemp,
|
||||
)}
|
||||
${this._renderInfoRow(
|
||||
"curr_hotbed_temp",
|
||||
this.printerStateCurrHotbedTemp,
|
||||
)}
|
||||
${this._renderInfoRow(
|
||||
"target_nozzle_temp",
|
||||
this.printerStateTargetNozzleTemp,
|
||||
)}
|
||||
${this._renderInfoRow(
|
||||
"target_hotbed_temp",
|
||||
this.printerStateTargetHotbedTemp,
|
||||
)}
|
||||
`
|
||||
: nothing}
|
||||
${this._renderInfoRow("job_state", this.jobStatePrintState)}
|
||||
${this._renderInfoRow("job_progress", this.jobStateProgress)}
|
||||
${this._renderOptionalInfoRow(
|
||||
"ace_fw_update_available",
|
||||
this.aceStateFwUpdateAvailable,
|
||||
)}
|
||||
${this._renderOptionalInfoRow(
|
||||
"drying_active",
|
||||
this.aceStateDryingActive,
|
||||
)}
|
||||
${this._renderOptionalInfoRow(
|
||||
"drying_progress",
|
||||
this.aceDryingProgress,
|
||||
)}
|
||||
</div>
|
||||
</printer-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
}
|
||||
printer-card {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
anycubic-printercard-card {
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
.ac-extra-printer-info {
|
||||
padding: 20px 40px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-heading {
|
||||
margin-right: 10px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.info-detail {
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { CSSResult, css } from "lit";
|
||||
|
||||
export const commonPrintStyle: CSSResult = css`
|
||||
:host {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
}
|
||||
ac-print-view {
|
||||
padding: 16px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
ha-alert {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.print-button {
|
||||
margin: auto;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,146 @@
|
||||
import { mdiPlay } from "@mdi/js";
|
||||
import { CSSResult, LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
import { commonPrintStyle } from "./styles";
|
||||
import { localize } from "../../../localize/localize";
|
||||
|
||||
import { platform } from "../../const";
|
||||
import { HASSDomEvent } from "../../fire_event";
|
||||
import { fireHaptic } from "../../fire_haptic";
|
||||
import { loadHaServiceControl } from "../../load-ha-elements";
|
||||
import {
|
||||
FormChangeDetail,
|
||||
HassDevice,
|
||||
HassPanel,
|
||||
HassProgressButton,
|
||||
HassRoute,
|
||||
HassServiceError,
|
||||
HomeAssistant,
|
||||
LitTemplateResult,
|
||||
} from "../../types";
|
||||
|
||||
export class AnycubicViewPrintBase extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
public language!: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property()
|
||||
public route!: HassRoute;
|
||||
|
||||
@property()
|
||||
public panel!: HassPanel;
|
||||
|
||||
@property({ attribute: "selected-printer-id" })
|
||||
public selectedPrinterID: string | undefined;
|
||||
|
||||
@property({ attribute: "selected-printer-device" })
|
||||
public selectedPrinterDevice: HassDevice | undefined;
|
||||
|
||||
@state() private _scriptData: Record<
|
||||
string,
|
||||
string | Record<string, string> | undefined
|
||||
> = {};
|
||||
|
||||
@state()
|
||||
private _error: string | undefined;
|
||||
|
||||
@state()
|
||||
protected _serviceName: string = "";
|
||||
|
||||
@state()
|
||||
private _buttonPrint: string;
|
||||
|
||||
@state()
|
||||
private _buttonProgress: boolean = false;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
await loadHaServiceControl();
|
||||
}
|
||||
|
||||
protected override willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (changedProperties.has("language")) {
|
||||
this._buttonPrint = localize("common.actions.print", this.language);
|
||||
}
|
||||
|
||||
if (changedProperties.has("selectedPrinterDevice")) {
|
||||
if (this.selectedPrinterDevice) {
|
||||
const srvName = `${platform}.${this._serviceName}`;
|
||||
this._scriptData = {
|
||||
...this._scriptData,
|
||||
action: srvName,
|
||||
service: srvName,
|
||||
data: {
|
||||
...((this._scriptData.data as object | undefined) ||
|
||||
({} as object)),
|
||||
config_entry: this.selectedPrinterDevice.primary_config_entry,
|
||||
device_id: this.selectedPrinterDevice.id,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(): LitTemplateResult {
|
||||
return html`
|
||||
<ac-print-view elevation="2">
|
||||
<ha-service-control
|
||||
hidePicker
|
||||
.hass=${this.hass}
|
||||
.value=${this._scriptData}
|
||||
.showAdvanced=${true}
|
||||
.narrow=${this.narrow}
|
||||
@value-changed=${this._scriptDataChanged}
|
||||
></ha-service-control>
|
||||
${this._error !== undefined
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-progress-button
|
||||
class="print-button"
|
||||
raised
|
||||
@click=${this._runScript}
|
||||
.progress=${this._buttonProgress}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlay}></ha-svg-icon>
|
||||
${this._buttonPrint}
|
||||
</ha-progress-button>
|
||||
</ac-print-view>
|
||||
`;
|
||||
}
|
||||
|
||||
private _scriptDataChanged = (ev: HASSDomEvent<FormChangeDetail>): void => {
|
||||
this._scriptData = { ...this._scriptData, ...ev.detail.value };
|
||||
this._error = undefined;
|
||||
};
|
||||
|
||||
private _runScript = (ev: Event): void => {
|
||||
const button = ev.currentTarget as unknown as HassProgressButton;
|
||||
this._error = undefined;
|
||||
ev.stopPropagation();
|
||||
this._buttonProgress = true;
|
||||
fireHaptic();
|
||||
this.hass
|
||||
.callService(platform, this._serviceName, this._scriptData.data as object)
|
||||
.then(() => {
|
||||
button.actionSuccess();
|
||||
this._buttonProgress = false;
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
this._error = (e as HassServiceError).message;
|
||||
button.actionError();
|
||||
this._buttonProgress = false;
|
||||
});
|
||||
};
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
${commonPrintStyle}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { AnycubicViewPrintBase } from "./view-print-base";
|
||||
|
||||
@customElement("anycubic-view-print-no_cloud_save")
|
||||
export class AnycubicViewPrintNoCloudSave extends AnycubicViewPrintBase {
|
||||
@state()
|
||||
protected _serviceName: string = "print_and_upload_no_cloud_save";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { AnycubicViewPrintBase } from "./view-print-base";
|
||||
|
||||
@customElement("anycubic-view-print-save_in_cloud")
|
||||
export class AnycubicViewPrintSaveInCloud extends AnycubicViewPrintBase {
|
||||
@state()
|
||||
protected _serviceName: string = "print_and_upload_save_in_cloud";
|
||||
}
|
||||
25
custom_components/kobrax_lan/frontend_panel/tsconfig.json
Normal file
25
custom_components/kobrax_lan/frontend_panel/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"noEmit": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"strictPropertyInitialization": false,
|
||||
"allowSyntheticDefaultImports": true
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,106 @@ SENSORS: tuple[KobraXSensorDescription, ...] = (
|
||||
value_key="print_duration",
|
||||
icon="mdi:timer-outline",
|
||||
),
|
||||
# Compatibility aliases used by the kobrax-lan-card (ported from anycubic card)
|
||||
KobraXSensorDescription(
|
||||
key="job_state",
|
||||
name="Job State",
|
||||
value_key="print_state",
|
||||
icon="mdi:state-machine",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="current_status",
|
||||
name="Current Status",
|
||||
value_key="print_state",
|
||||
icon="mdi:information-outline",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_progress",
|
||||
name="Job Progress",
|
||||
value_key="progress",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
icon="mdi:percent",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_time_elapsed",
|
||||
name="Job Time Elapsed",
|
||||
value_key="print_duration",
|
||||
icon="mdi:timer-outline",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_time_remaining",
|
||||
name="Job Time Remaining",
|
||||
value_key="remain_time",
|
||||
icon="mdi:timer-sand",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="hotbed_temperature",
|
||||
name="Hotbed Temperature",
|
||||
value_key="bed_temp",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="nozzle_temperature",
|
||||
name="Nozzle Temperature",
|
||||
value_key="nozzle_temp",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="target_hotbed_temperature",
|
||||
name="Target Hotbed Temperature",
|
||||
value_key="bed_target",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="target_nozzle_temperature",
|
||||
name="Target Nozzle Temperature",
|
||||
value_key="nozzle_target",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_name",
|
||||
name="Job Name",
|
||||
value_key="filename",
|
||||
icon="mdi:file",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_current_layer",
|
||||
name="Job Current Layer",
|
||||
value_key="curr_layer",
|
||||
icon="mdi:layers-triple",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_total_layers",
|
||||
name="Job Total Layers",
|
||||
value_key="total_layers",
|
||||
icon="mdi:layers",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="job_speed_mode",
|
||||
name="Job Speed Mode",
|
||||
value_key="print_speed_mode",
|
||||
icon="mdi:speedometer",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="fan_speed",
|
||||
name="Fan Speed",
|
||||
value_key="fan_speed",
|
||||
icon="mdi:fan",
|
||||
),
|
||||
KobraXSensorDescription(
|
||||
key="ace_spools",
|
||||
name="ACE Spools",
|
||||
value_key="ams_slots",
|
||||
icon="mdi:palette-swatch",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -110,10 +210,45 @@ class KobraXSensor(KobraXEntity, SensorEntity):
|
||||
@property
|
||||
def native_value(self) -> Any:
|
||||
value = self.state_data.get(self.entity_description.value_key)
|
||||
if self.entity_description.value_key == "progress" and value is not None:
|
||||
if self.entity_description.key in {"progress", "job_progress"} and value is not None:
|
||||
return round(float(value) * 100, 1)
|
||||
if self.entity_description.key == "job_speed_mode":
|
||||
mode = int(value) if value is not None else 2
|
||||
mapping = {1: "Slow", 2: "Normal", 3: "Fast"}
|
||||
return mapping.get(mode, "Normal")
|
||||
if self.entity_description.key == "ace_spools":
|
||||
return "active" if isinstance(value, list) and len(value) > 0 else "inactive"
|
||||
return value
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
if self.entity_description.key == "current_status":
|
||||
material_type = self.state_data.get("material_type") or "Filament"
|
||||
return {"material_type": material_type}
|
||||
|
||||
if self.entity_description.key == "job_speed_mode":
|
||||
mode = int(self.state_data.get("print_speed_mode") or 2)
|
||||
return {
|
||||
"available_modes": [
|
||||
{"mode": 1, "description": "Slow"},
|
||||
{"mode": 2, "description": "Normal"},
|
||||
{"mode": 3, "description": "Fast"},
|
||||
],
|
||||
"print_speed_mode_code": mode,
|
||||
}
|
||||
|
||||
if self.entity_description.key in {"target_nozzle_temperature", "target_hotbed_temperature"}:
|
||||
return {
|
||||
"limit_min": 0,
|
||||
"limit_max": 400 if self.entity_description.key == "target_nozzle_temperature" else 200,
|
||||
}
|
||||
|
||||
if self.entity_description.key == "ace_spools":
|
||||
slots = self.state_data.get("ams_slots")
|
||||
return {"slots": slots if isinstance(slots, list) else []}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class KobraXFilamentSlotSensor(KobraXEntity, SensorEntity):
|
||||
def __init__(self, coordinator, entry, slot_index: int, field: str) -> None:
|
||||
|
||||
153
custom_components/kobrax_lan/services.py
Normal file
153
custom_components/kobrax_lan/services.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .api import KobraXApiError
|
||||
from .const import DOMAIN
|
||||
|
||||
SERVICE_CHANGE_PRINT_SPEED_MODE = "change_print_speed_mode"
|
||||
SERVICE_CHANGE_PRINT_TARGET_NOZZLE_TEMPERATURE = "change_print_target_nozzle_temperature"
|
||||
SERVICE_CHANGE_PRINT_TARGET_HOTBED_TEMPERATURE = "change_print_target_hotbed_temperature"
|
||||
|
||||
ATTR_CONFIG_ENTRY = "config_entry"
|
||||
ATTR_DEVICE_ID = "device_id"
|
||||
ATTR_PRINTER_ID = "printer_id"
|
||||
ATTR_SPEED_MODE = "speed_mode"
|
||||
ATTR_TEMPERATURE = "temperature"
|
||||
|
||||
SERVICE_SCHEMAS: dict[str, vol.Schema] = {
|
||||
SERVICE_CHANGE_PRINT_SPEED_MODE: vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_CONFIG_ENTRY): cv.string,
|
||||
vol.Optional(ATTR_DEVICE_ID): cv.string,
|
||||
vol.Optional(ATTR_PRINTER_ID): vol.Any(cv.positive_int, cv.string),
|
||||
vol.Required(ATTR_SPEED_MODE): vol.All(vol.Coerce(int), vol.Range(min=1, max=3)),
|
||||
}
|
||||
),
|
||||
SERVICE_CHANGE_PRINT_TARGET_NOZZLE_TEMPERATURE: vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_CONFIG_ENTRY): cv.string,
|
||||
vol.Optional(ATTR_DEVICE_ID): cv.string,
|
||||
vol.Optional(ATTR_PRINTER_ID): vol.Any(cv.positive_int, cv.string),
|
||||
vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), vol.Range(min=0, max=400)),
|
||||
}
|
||||
),
|
||||
SERVICE_CHANGE_PRINT_TARGET_HOTBED_TEMPERATURE: vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_CONFIG_ENTRY): cv.string,
|
||||
vol.Optional(ATTR_DEVICE_ID): cv.string,
|
||||
vol.Optional(ATTR_PRINTER_ID): vol.Any(cv.positive_int, cv.string),
|
||||
vol.Required(ATTR_TEMPERATURE): vol.All(vol.Coerce(float), vol.Range(min=0, max=200)),
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _resolve_entry_id(hass: HomeAssistant, call: ServiceCall) -> str:
|
||||
requested = call.data.get(ATTR_CONFIG_ENTRY)
|
||||
domain_data = hass.data.get(DOMAIN, {})
|
||||
|
||||
if requested:
|
||||
if requested in domain_data:
|
||||
return requested
|
||||
raise ServiceValidationError(f"Unknown config_entry for {DOMAIN}: {requested}")
|
||||
|
||||
entry_ids = [entry_id for entry_id, value in domain_data.items() if isinstance(value, dict) and "api" in value]
|
||||
if len(entry_ids) == 1:
|
||||
return entry_ids[0]
|
||||
|
||||
raise ServiceValidationError(
|
||||
"Multiple Kobra X LAN entries loaded. Include config_entry in the service call."
|
||||
)
|
||||
|
||||
|
||||
async def _handle_change_print_speed_mode(hass: HomeAssistant, call: ServiceCall) -> None:
|
||||
entry_id = _resolve_entry_id(hass, call)
|
||||
api = hass.data[DOMAIN][entry_id]["api"]
|
||||
coordinator = hass.data[DOMAIN][entry_id]["coordinator"]
|
||||
|
||||
mode = int(call.data[ATTR_SPEED_MODE])
|
||||
|
||||
try:
|
||||
await api.async_set_speed_mode(mode)
|
||||
await coordinator.async_request_refresh()
|
||||
except KobraXApiError as err:
|
||||
raise HomeAssistantError(str(err)) from err
|
||||
|
||||
|
||||
async def _handle_change_print_target_nozzle_temperature(hass: HomeAssistant, call: ServiceCall) -> None:
|
||||
entry_id = _resolve_entry_id(hass, call)
|
||||
api = hass.data[DOMAIN][entry_id]["api"]
|
||||
coordinator = hass.data[DOMAIN][entry_id]["coordinator"]
|
||||
|
||||
temperature = float(call.data[ATTR_TEMPERATURE])
|
||||
|
||||
try:
|
||||
await api.async_set_temperature(nozzle=temperature, bed=None)
|
||||
await coordinator.async_request_refresh()
|
||||
except KobraXApiError as err:
|
||||
raise HomeAssistantError(str(err)) from err
|
||||
|
||||
|
||||
async def _handle_change_print_target_hotbed_temperature(hass: HomeAssistant, call: ServiceCall) -> None:
|
||||
entry_id = _resolve_entry_id(hass, call)
|
||||
api = hass.data[DOMAIN][entry_id]["api"]
|
||||
coordinator = hass.data[DOMAIN][entry_id]["coordinator"]
|
||||
|
||||
temperature = float(call.data[ATTR_TEMPERATURE])
|
||||
|
||||
try:
|
||||
await api.async_set_temperature(nozzle=None, bed=temperature)
|
||||
await coordinator.async_request_refresh()
|
||||
except KobraXApiError as err:
|
||||
raise HomeAssistantError(str(err)) from err
|
||||
|
||||
|
||||
def async_register_services(hass: HomeAssistant) -> None:
|
||||
async def _service_change_print_speed_mode(call: ServiceCall) -> None:
|
||||
await _handle_change_print_speed_mode(hass, call)
|
||||
|
||||
async def _service_change_print_target_nozzle_temperature(call: ServiceCall) -> None:
|
||||
await _handle_change_print_target_nozzle_temperature(hass, call)
|
||||
|
||||
async def _service_change_print_target_hotbed_temperature(call: ServiceCall) -> None:
|
||||
await _handle_change_print_target_hotbed_temperature(hass, call)
|
||||
|
||||
if not hass.services.has_service(DOMAIN, SERVICE_CHANGE_PRINT_SPEED_MODE):
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_CHANGE_PRINT_SPEED_MODE,
|
||||
_service_change_print_speed_mode,
|
||||
schema=SERVICE_SCHEMAS[SERVICE_CHANGE_PRINT_SPEED_MODE],
|
||||
)
|
||||
|
||||
if not hass.services.has_service(DOMAIN, SERVICE_CHANGE_PRINT_TARGET_NOZZLE_TEMPERATURE):
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_CHANGE_PRINT_TARGET_NOZZLE_TEMPERATURE,
|
||||
_service_change_print_target_nozzle_temperature,
|
||||
schema=SERVICE_SCHEMAS[SERVICE_CHANGE_PRINT_TARGET_NOZZLE_TEMPERATURE],
|
||||
)
|
||||
|
||||
if not hass.services.has_service(DOMAIN, SERVICE_CHANGE_PRINT_TARGET_HOTBED_TEMPERATURE):
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_CHANGE_PRINT_TARGET_HOTBED_TEMPERATURE,
|
||||
_service_change_print_target_hotbed_temperature,
|
||||
schema=SERVICE_SCHEMAS[SERVICE_CHANGE_PRINT_TARGET_HOTBED_TEMPERATURE],
|
||||
)
|
||||
|
||||
|
||||
def async_unregister_services(hass: HomeAssistant) -> None:
|
||||
for service_name in (
|
||||
SERVICE_CHANGE_PRINT_SPEED_MODE,
|
||||
SERVICE_CHANGE_PRINT_TARGET_NOZZLE_TEMPERATURE,
|
||||
SERVICE_CHANGE_PRINT_TARGET_HOTBED_TEMPERATURE,
|
||||
):
|
||||
if hass.services.has_service(DOMAIN, service_name):
|
||||
hass.services.async_remove(DOMAIN, service_name)
|
||||
71
custom_components/kobrax_lan/services.yaml
Normal file
71
custom_components/kobrax_lan/services.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
change_print_speed_mode:
|
||||
fields:
|
||||
config_entry:
|
||||
required: false
|
||||
selector:
|
||||
config_entry:
|
||||
integration: kobrax_lan
|
||||
device_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
printer_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
speed_mode:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
min: 1
|
||||
max: 3
|
||||
step: 1
|
||||
mode: box
|
||||
|
||||
change_print_target_nozzle_temperature:
|
||||
fields:
|
||||
config_entry:
|
||||
required: false
|
||||
selector:
|
||||
config_entry:
|
||||
integration: kobrax_lan
|
||||
device_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
printer_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
temperature:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 400
|
||||
step: 1
|
||||
mode: box
|
||||
|
||||
change_print_target_hotbed_temperature:
|
||||
fields:
|
||||
config_entry:
|
||||
required: false
|
||||
selector:
|
||||
config_entry:
|
||||
integration: kobrax_lan
|
||||
device_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
printer_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
temperature:
|
||||
required: true
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 200
|
||||
step: 1
|
||||
mode: box
|
||||
Reference in New Issue
Block a user