Skip to content

Commit 507d9b2

Browse files
authored
Release v0.5.0 (#16)
* Labels instead of icons #7 * String replace option in KeywordString * String replace option in KeywordString * Bumping version * Removed reduncant old keyword replace logic
1 parent b02252b commit 507d9b2

File tree

7 files changed

+124
-27
lines changed

7 files changed

+124
-27
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This component can be used as entity as well
4444
| name | String | **(required)** | v0.1.0 | Name of the attribute
4545
| icon | String | | v0.1.0 | Icon override (there are default icons for most of the available attributes)
4646
| url | [KString](#keywordstring) \| Boolean | | v0.2.0 | Url to open on click/tap. (there are default urls for most of the available attributes, so you can just use `true`)
47+
| label | [KString](#keywordstring) | | v0.5.0 | Label/text which will be shown instead of the icon
4748

4849
### KeywordString
4950

@@ -53,6 +54,12 @@ E.g. `"Card version {latest_release_tag}"` becomes `"Card version v1.5.0"`
5354

5455
![image](https://user-images.githubusercontent.com/8268674/95771623-4ddde880-0cb3-11eb-9265-57876a08bd6e.png)
5556

57+
#### Converting keyword value
58+
59+
You can do simple replace operation on the value e.g.: `"{name:Github=Project}"`. It will replace `"Github"` string in the `name` value with `"Project"`, so if your name attribute is `"Github github-flexi-card"` then the final result will be `"Project github-flexi-card"`.
60+
61+
Note: It is very simple replace machanism, it is case sensitive, replaces only first match and it doesn't have any escape chars so you cannot use characters like `=` or `:` in the search word nor target word.
62+
5663
## Configuration examples
5764

5865
### Card
@@ -143,6 +150,28 @@ entities:
143150
- sensor.urleditorpro
144151
```
145152

153+
### Labels instead of icons
154+
155+
![image](https://user-images.githubusercontent.com/8268674/96354074-37c49380-10ca-11eb-9151-829e5c37f877.png)
156+
157+
```yaml
158+
type: 'custom:github-flexi-card'
159+
title: Labels instead of icons
160+
url: true
161+
attribute_urls: true
162+
attributes:
163+
- name: views
164+
label: Views
165+
- name: stargazers
166+
label: Stars
167+
- name: open_issues
168+
label: Issues
169+
entities:
170+
- sensor.battery_state_card
171+
- sensor.hideseek_mod
172+
- sensor.urleditorpro
173+
```
174+
146175
## How to install?
147176

148177
Install HACS and add this repo as custom repository (lovelace plugins). Click on "Install".

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "github-flexi-card",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"description": "Home assistant github card",
55
"main": "dist/github-flexi-card.js",
66
"scripts": {

src/custom-elements/entity.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
:host {
2+
--attribute-icon-size: 20px;
3+
}
4+
15
.truncate {
26
white-space: nowrap;
37
text-overflow: ellipsis;
@@ -39,7 +43,7 @@
3943
}
4044

4145
.state {
42-
--mdc-icon-size: 20px;
46+
--mdc-icon-size: var(--attribute-icon-size);
4347
display: flex;
4448
margin-left: 7px;
4549
}
@@ -62,6 +66,12 @@
6266
.compact-view .state > div {
6367
line-height: normal;
6468
}
69+
.compact-view .state > .label {
70+
margin-top: 1px; /* fix for small misalignment if you have icon next to label */
71+
color: var(--primary-color);
72+
line-height: var(--attribute-icon-size);
73+
}
74+
6575
.compact-view .state > * {
6676
margin: 0;
6777
}

src/custom-elements/entity.ts

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { HomeAssistant } from "../ha-types";
2+
import { KeywordStringProcessor } from "../keyword-processor";
23
import { html, LitElement } from "../lit-element";
3-
import { IEntityConfig, IAttribute } from "../types";
4+
import { IEntityConfig, IMap } from "../types";
45
import { logError } from "../utils";
56
import styles from "./entity-styles";
67

7-
interface IMap<T> {
8-
[key: string]: T
9-
}
10-
118
interface IAttributeViewData {
129
value: string,
1310
tooltip: string,
@@ -67,14 +64,16 @@ export class GithubEntity extends LitElement {
6764
return;
6865
}
6966

70-
this.name = replaceKeywordsWithData(entityData.attributes, this.config.name) || entityData.attributes["friendly_name"];
67+
const keywordProcessor = new KeywordStringProcessor(entityData.attributes, entityData.state);
68+
69+
this.name = keywordProcessor.process(this.config.name) || entityData.attributes["friendly_name"];
7170
this.icon = this.config.icon || entityData.attributes["icon"];
7271

7372
if (this.config.secondary_info) {
74-
this.secondaryInfo = replaceKeywordsWithData(entityData.attributes, this.config.secondary_info) as string;
73+
this.secondaryInfo = keywordProcessor.process(this.config.secondary_info) as string;
7574
}
7675

77-
const newStats = getAttributesViewData(this.config, entityData.attributes);
76+
const newStats = getAttributesViewData(this.config, entityData.attributes, keywordProcessor);
7877

7978
// check to avoid unnecessary re-rendering
8079
if (JSON.stringify(newStats) != JSON.stringify(this.attributesData)) {
@@ -84,7 +83,7 @@ export class GithubEntity extends LitElement {
8483
// check whether we need to update the action
8584
if (this.url != this.config.url) {
8685
this.url = this.config.url;
87-
this.action = getAction("home", this.url, entityData.attributes);
86+
this.action = getAction("home", this.url, entityData.attributes["path"], keywordProcessor);
8887
}
8988
}
9089

@@ -136,18 +135,12 @@ export class GithubEntity extends LitElement {
136135
*/
137136
const attributeView = (attr: IAttributeViewData) => html`
138137
<div class="state${attr.action ? " clickable" : ""}" @click="${attr.action}" title="${attr.tooltip}">
139-
<ha-icon icon="${attr.icon}" style="color: var(--primary-color)">
140-
</ha-icon>
138+
${attr.label && html`<div class="label">${attr.label}</div>`}
139+
${(attr.icon && !attr.label) ? html`<ha-icon icon="${attr.icon}" style="color: var(--primary-color)"></ha-icon>` : null}
141140
<div>${attr.value}</div>
142141
</div>
143142
`;
144143

145-
/**
146-
* Replaces keywords in given string with actual data
147-
*/
148-
const replaceKeywordsWithData = (data: IMap<string>, text?: string) =>
149-
text && text.replace(/\{([a-z0-9_]+)\}/g, (match, keyword) => data[keyword] !== undefined ? data[keyword] : match);
150-
151144
/**
152145
* Attribute name to icon map
153146
*/
@@ -182,14 +175,14 @@ const nameToUrlPathMap: IMap<string> = {
182175
/**
183176
* Creates action for clickable elements
184177
*/
185-
const getAction = (attributeName: string, url: boolean | string | undefined, data: IMap<string>): Function | undefined => {
178+
const getAction = (attributeName: string, url: boolean | string | undefined, path: string, keywordProcessor: KeywordStringProcessor): Function | undefined => {
186179
switch (typeof url) {
187180
case "boolean":
188181
if (!url) {
189182
return undefined;
190183
}
191184

192-
if (!data["path"]) {
185+
if (!path) {
193186
logError(`Cannot build url - entity path attribute is missing`);
194187
return undefined;
195188
}
@@ -199,9 +192,9 @@ const getAction = (attributeName: string, url: boolean | string | undefined, dat
199192
return undefined;
200193
}
201194

202-
return () => window.open(`https://github.com/${data["path"]}/${nameToUrlPathMap[attributeName]}`);
195+
return () => window.open(`https://github.com/${path}/${nameToUrlPathMap[attributeName]}`);
203196
case "string":
204-
return () => window.open(replaceKeywordsWithData(data, url));
197+
return () => window.open(keywordProcessor.process(url));
205198
case "undefined":
206199
// we don't do anything
207200
break;
@@ -215,18 +208,20 @@ const getAction = (attributeName: string, url: boolean | string | undefined, dat
215208
/**
216209
* Gets list of attributes data to render
217210
*/
218-
const getAttributesViewData = (config: IEntityConfig, data: IMap<string>): IAttributeViewData[] =>
211+
const getAttributesViewData = (config: IEntityConfig, data: IMap<string>, keywordProcessor: KeywordStringProcessor): IAttributeViewData[] =>
219212
(config.attributes || []).map(a => {
220213
return {
221214
value: data[a.name],
222215
tooltip: attributeNameToTooltip(a.name),
223216
icon: a.icon || nameToIconMap[a.name],
224-
label: a.label && replaceKeywordsWithData(data, a.label),
217+
label: a.label && keywordProcessor.process(a.label),
225218
action: getAction(
226219
a.name,
227220
// if attrib url property is missing use the entity-level setting
228221
a.url !== undefined ? a.url : config.attribute_urls,
229-
data),
222+
data["path"],
223+
keywordProcessor
224+
),
230225
}
231226
});
232227

src/keyword-processor.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { IMap } from "./types";
2+
3+
/**
4+
* Class for processing keyword strings
5+
*/
6+
export class KeywordStringProcessor {
7+
constructor(private data: IMap<string>, state: string) {
8+
if (!this.data["state"]) {
9+
this.data["state"] = state;
10+
}
11+
}
12+
13+
/**
14+
* Replaces keywords in given string with the data
15+
*/
16+
process(text?: string): string | undefined {
17+
if (!text) {
18+
return text;
19+
}
20+
21+
return text.replace(/\{([^\}]+)\}/g, (match, keyword) => this.replaceKeyword(keyword, match));
22+
}
23+
24+
/**
25+
* Converts keyword in the final value
26+
*/
27+
private replaceKeyword(keyword: string, defaultValue: string): string {
28+
const chunks = keyword.split(":");
29+
const attributeName = chunks[0];
30+
const processingDetails = chunks[1];
31+
32+
const value = this.data[attributeName];
33+
34+
if (value === undefined) {
35+
return defaultValue;
36+
}
37+
38+
const processor = this.getProcessor(processingDetails);
39+
40+
return processor(value);
41+
}
42+
43+
/**
44+
* Returns value processor (if any processing options were specified)
45+
*/
46+
private getProcessor(processingDetails: string): { (text: string): string } {
47+
if (!processingDetails) {
48+
return (text) => text;
49+
}
50+
51+
// check if we should replace string in the value
52+
if (processingDetails.includes("=")) {
53+
const replaceDataChunks = processingDetails.split("=");
54+
return (text) => text.replace(replaceDataChunks[0], replaceDataChunks[1]);
55+
}
56+
57+
return (text) => text;
58+
}
59+
}

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@ export interface IAttribute {
2121
icon?: string,
2222
label?: string,
2323
url?: string | boolean,
24+
}
25+
26+
export interface IMap<T> {
27+
[key: string]: T
2428
}

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const printVersion = () => console.info(
2-
"%c GITHUB-FLEXI-CARD %c 0.4.0",
2+
"%c GITHUB-FLEXI-CARD %c 0.5.0",
33
"color: white; background: #cca900; font-weight: 700;",
44
"color: #cca900; background: white; font-weight: 700;",
55
);

0 commit comments

Comments
 (0)