diff --git a/README.md b/README.md index 789ab97..d86b158 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,18 @@ This card provides a possibility to generate QRCode in Home Assistant interface. Default Description + + + General options + + + + title + string + no + empty + Title for the card + source string diff --git a/package.json b/package.json index d065ea4..e75d65e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qr-code-card", - "version": "v1.0.1", + "version": "v1.0.2", "description": "QR code card", "keywords": [ "home-assistant", diff --git a/src/editor.ts b/src/editor.ts index fd10fc5..01fdb60 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -11,7 +11,7 @@ import { } from "./types/types"; import { localizeWithHass } from "./localize/localize"; import { SourceType } from "./models/source-type"; -import { AuthenticationType } from "./models/authentication-type"; +import { AuthenticationType, is_password_protected } from "./models/authentication-type"; import { EDITOR_CUSTOM_ELEMENT_NAME } from "./const"; @@ -19,6 +19,7 @@ import { EDITOR_CUSTOM_ELEMENT_NAME } from "./const"; export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor { @property({attribute: false}) public hass?: HomeAssistant; @state() private _config?: QRCodeCardConfig; + @state() private _unmaskedPassword = false; private _initialized = false; public setConfig(config: QRCodeCardConfig): void { @@ -32,6 +33,10 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor { return true; } + get _title(): string { + return this._config?.title || ""; + } + get _source(): SourceType | undefined { return this._config?.source; } @@ -65,7 +70,7 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor { const config = this._config as EntitySourceConfig | undefined; return config?.entity || "" } - + private _localize(ts: TranslatableString): string { return localizeWithHass(ts, this.hass, this._config); } @@ -79,6 +84,14 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor { return html`
+
+ +
+
- ${this._auth_type != AuthenticationType.NOPASS ? html` + ${is_password_protected(this._auth_type) ? html`
+ @value-changed=${this._valueChanged}> + + + + +
` : ""}
@@ -181,6 +206,10 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor { this._updateConfig(ev.target.configValue, value); } + private _toggleUnmaskedPassword(): void { + this._unmaskedPassword = !this._unmaskedPassword; + } + private _updateConfig(key: string, value: any) { if (!this._config || !this.hass) { return; @@ -210,9 +239,17 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor { margin: 8px; display: grid; } - - ha-formfield { - padding: 8px; + + .values ha-icon-button { + flex-direction: column; + width: 24px; + height: 24px; + --mdc-icon-button-size: 24px; + color: var(--secondary-text-color); + } + + .values ha-icon { + display: flex; } `; } diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 5ea5b2d..9eb1168 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -6,6 +6,7 @@ }, "editor": { "label": { + "title": "Title (optional)", "source": "Source type (required)", "text": "Text (required)", "entity": "Entity (required)", @@ -14,6 +15,10 @@ "password": "Password (required)", "is_hidden": "Hidden (optional)" }, + "title": { + "show_password": "Show password", + "hide_password": "Hide password" + }, "options": { "source": { "text": "Text", diff --git a/src/models/authentication-type.ts b/src/models/authentication-type.ts index d98d277..c3369d4 100644 --- a/src/models/authentication-type.ts +++ b/src/models/authentication-type.ts @@ -3,3 +3,9 @@ export enum AuthenticationType { WPA = "WPA", NOPASS = "nopass", } + +const PasswordAuthenticationTypes = [AuthenticationType.WEP, AuthenticationType.WPA]; + +export function is_password_protected(auth_type: AuthenticationType | undefined): boolean { + return auth_type !== undefined && PasswordAuthenticationTypes.includes(auth_type); +} diff --git a/src/models/generator.ts b/src/models/generator.ts index 80e3df7..8b02376 100644 --- a/src/models/generator.ts +++ b/src/models/generator.ts @@ -7,7 +7,7 @@ import { TextSourceConfig, WiFiSourceConfig, } from "../types/types"; -import { AuthenticationType } from "./authentication-type"; +import { is_password_protected } from "./authentication-type"; import { SourceType } from "./source-type"; import { localize } from "../localize/localize"; import { HomeAssistant } from "custom-card-helpers"; @@ -66,7 +66,7 @@ class WiFiQRCodeGenerator extends QRCodeGenerator { protected get input(): string { let text = `WIFI:T:${this.config.auth_type || ""};S:${this._escape(this.config.ssid || "")};`; - if (this.config.auth_type !== AuthenticationType.NOPASS) { + if (is_password_protected(this.config.auth_type)) { text += `P:${this._escape(this.config.password || "")};` } diff --git a/src/qr-code-card.ts b/src/qr-code-card.ts index 93e6a98..7b9a296 100644 --- a/src/qr-code-card.ts +++ b/src/qr-code-card.ts @@ -113,6 +113,7 @@ export class QRCodeCard extends LitElement { return html` + ${(this.config?.title ?? "").length > 0 ? html`

${this.config.title}

`: ""}
diff --git a/src/types/types.ts b/src/types/types.ts index 72eaffc..274fce7 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -17,9 +17,11 @@ export type Language = string | undefined; export type DataUrl = string; export type QRCodeGeneratorClass = new (...args: any[]) => T; +export type QRCodeValidatorClass = new (...args: any[]) => T; export interface BaseQRCodeCardConfig extends LovelaceCardConfig { readonly language?: Language; + readonly title?: string; readonly source: SourceType; } diff --git a/src/validators.ts b/src/validators.ts index 8ff49b8..480d1ac 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -3,59 +3,110 @@ import { QRCodeCardConfig, TextSourceConfig, WiFiSourceConfig, - EntitySourceConfig + EntitySourceConfig, + QRCodeValidatorClass } from "./types/types"; import { localize } from "./localize/localize"; import { SourceType } from "./models/source-type"; -import { AuthenticationType } from "./models/authentication-type"; +import { AuthenticationType, is_password_protected } from "./models/authentication-type"; + + +abstract class Validator { + + protected readonly config: T; + + public constructor(config: T) { + this.config = config; + } + + public validate(): string[] { + return this._validate() + } + + protected abstract _validate(): string[] -function validateSource(source: SourceType): string[] { - if (!source) return ["validation.source.missing"]; - if (!Object.values(SourceType).includes(source)) return ["validation.source.invalid"]; - return []; } -function validateTextConfig(text: string): string[] { - if (!text) return ["validation.text.missing"]; - return []; +class SourceValidator extends Validator { + protected _validate(): string[] { + const errors: string[] = []; + + if (!this.config.source) { + errors.push("validation.source.missing"); + } + else { + if (!Object.values(SourceType).includes(this.config.source)) { + errors.push("validation.source.invalid"); + } + } + + return errors; + } } +class TextValidator extends Validator { + protected _validate(): string[] { + const errors: string[] = []; -function validateWiFiConfig(auth_type: AuthenticationType, ssid: string, password: string | undefined): string[] { - if (!auth_type) return ["validation.auth_type.missing"]; - if (!Object.values(AuthenticationType).includes(auth_type)) return ["validation.auth_type.invalid"]; + if (!this.config.text) errors.push("validation.text.missing"); - const errors: string[] = []; - if (!ssid) { - errors.push("validation.ssid.missing"); + return errors; } - if (auth_type != AuthenticationType.NOPASS && !password) { - errors.push("validation.password.missing"); +} + +class WiFiValidator extends Validator { + protected _validate(): string[] { + const errors: string[] = []; + + // Validate auth type + if (!this.config.auth_type) { + errors.push("validation.auth_type.missing"); + } + else { + if (!Object.values(AuthenticationType).includes(this.config.auth_type)){ + errors.push("validation.auth_type.invalid"); + } + } + + // Validate ssid + if (!this.config.ssid) { + errors.push("validation.ssid.missing"); + } + + // Validate password + if (is_password_protected(this.config.auth_type) && !this.config.password) { + errors.push("validation.password.missing"); + } + + return errors; } - return errors; } -function validateEntityConfig(entity: string): string[] { - if (!entity) return ["validation.entity.missing"]; - return []; +class EntityValidator extends Validator { + protected _validate(): string[] { + const errors: string[] = []; + + if (!this.config.entity) errors.push("validation.entity.missing"); + + return errors; + } } +const validatorMap = new Map>>([ + [SourceType.TEXT, TextValidator], + [SourceType.WIFI, WiFiValidator], + [SourceType.ENTITY, EntityValidator] +]); + export function validateConfig(config: QRCodeCardConfig): string[] { const errors: TranslatableString[] = []; - validateSource(config.source).forEach(e => errors.push(e)); - switch (config.source) { - case SourceType.TEXT: - config = config as TextSourceConfig; - validateTextConfig(config.text).forEach(e => errors.push(e)); - break; - case SourceType.WIFI: - config = config as WiFiSourceConfig; - validateWiFiConfig(config.auth_type, config.ssid, config.password).forEach(e => errors.push(e)); - break; - case SourceType.ENTITY: - config = config as EntitySourceConfig; - validateEntityConfig(config.entity).forEach(e => errors.push(e)); - break; + + new SourceValidator(config).validate().forEach(e => errors.push(e)); + + if (errors.length == 0) { + const validatorCls = validatorMap.get(config.source); + if (validatorCls) new validatorCls(config).validate().forEach(e => errors.push(e)); } + return errors.map(e => localize(e, config.language)); }