Skip to content

Commit

Permalink
Merge pull request #1 from igor-panteleev/develop
Browse files Browse the repository at this point in the history
Release v1.0.2
  • Loading branch information
igor-panteleev authored Jan 20, 2022
2 parents f6c06d8 + 3be7dea commit fe8a57b
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 45 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ This card provides a possibility to generate QRCode in Home Assistant interface.
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td colspan="5" style="text-align: center">
General options
</td>
</tr>
<tr>
<td><code>title</code></td>
<td>string</td>
<td>no</td>
<td>empty</td>
<td>Title for the card</td>
</tr>
<tr>
<td><code>source</code></td>
<td>string</td>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "qr-code-card",
"version": "v1.0.1",
"version": "v1.0.2",
"description": "QR code card",
"keywords": [
"home-assistant",
Expand Down
51 changes: 44 additions & 7 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ 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";


@customElement(EDITOR_CUSTOM_ELEMENT_NAME)
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 {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -79,6 +84,14 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor {

return html`
<div class="card-config">
<div class="values">
<paper-input
label=${this._localize("editor.label.title")}
.value=${this._title}
.configValue=${"title"}
@value-changed=${this._valueChanged}></paper-input>
</div>
<div class="values">
<paper-dropdown-menu
label=${this._localize("editor.label.source")}
Expand Down Expand Up @@ -127,13 +140,25 @@ export class QRCodeCardEditor extends LitElement implements LovelaceCardEditor {
.configValue=${"ssid"}
@value-changed=${this._valueChanged}></paper-input>
</div>
${this._auth_type != AuthenticationType.NOPASS ? html`
${is_password_protected(this._auth_type) ? html`
<div class="values">
<paper-input
type=${this._unmaskedPassword ? "text" : "password"}
label=${this._localize("editor.label.password")}
.value=${this._password}
.configValue=${"password"}
@value-changed=${this._valueChanged}></paper-input>
@value-changed=${this._valueChanged}>
<ha-icon-button
toggles
.active=${this._unmaskedPassword}
title=${this._unmaskedPassword ? this._localize("editor.title.hide_password") : this._localize("editor.title.show_password")}
slot="suffix"
@click=${this._toggleUnmaskedPassword}>
<ha-icon
.icon=${this._unmaskedPassword ? "hass:eye-off" : "hass:eye"}>
</ha-icon>
</ha-icon-button>
</paper-input>
</div>
` : ""}
<div class="values">
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
`;
}
Expand Down
5 changes: 5 additions & 0 deletions src/localize/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
},
"editor": {
"label": {
"title": "Title (optional)",
"source": "Source type (required)",
"text": "Text (required)",
"entity": "Entity (required)",
Expand All @@ -14,6 +15,10 @@
"password": "Password (required)",
"is_hidden": "Hidden (optional)"
},
"title": {
"show_password": "Show password",
"hide_password": "Hide password"
},
"options": {
"source": {
"text": "Text",
Expand Down
6 changes: 6 additions & 0 deletions src/models/authentication-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 2 additions & 2 deletions src/models/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -66,7 +66,7 @@ class WiFiQRCodeGenerator extends QRCodeGenerator<WiFiSourceConfig> {
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 || "")};`
}

Expand Down
1 change: 1 addition & 0 deletions src/qr-code-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export class QRCodeCard extends LitElement {

return html`
<ha-card>
${(this.config?.title ?? "").length > 0 ? html`<h1 class="card-header">${this.config.title}</h1>`: ""}
<div class="qrcode-overlay">
<img class="qrcode" src="${this.dataUrl}">
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ export type Language = string | undefined;
export type DataUrl = string;

export type QRCodeGeneratorClass<T> = new (...args: any[]) => T;
export type QRCodeValidatorClass<T> = new (...args: any[]) => T;

export interface BaseQRCodeCardConfig extends LovelaceCardConfig {
readonly language?: Language;
readonly title?: string;
readonly source: SourceType;
}

Expand Down
121 changes: 86 additions & 35 deletions src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {

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<QRCodeCardConfig> {
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<TextSourceConfig> {
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<WiFiSourceConfig> {
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<EntitySourceConfig> {
protected _validate(): string[] {
const errors: string[] = [];

if (!this.config.entity) errors.push("validation.entity.missing");

return errors;
}
}

const validatorMap = new Map<SourceType, QRCodeValidatorClass<Validator<QRCodeCardConfig>>>([
[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));
}

0 comments on commit fe8a57b

Please sign in to comment.