Skip to content

Commit 4576f78

Browse files
authored
Merge pull request #8 from maxwroc/vNext
Release v0.2.0
2 parents 6f5725b + 2321d0e commit 4576f78

File tree

7 files changed

+141
-26
lines changed

7 files changed

+141
-26
lines changed

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ The aim of this card is to show all the data provided by github integration. You
2121
| Name | Type | Default | Since | Description |
2222
|:-----|:-----|:-----|:-----|:-----|
2323
| entity_id | String | **(required)** | v0.1.0 | Entity ID e.g. `sensor.my_github_project`
24-
| name | [KeywordString](#keywordstring) | | v0.1.0 | Name override
25-
| secondary_info | [KeywordString](#keywordstring) | | v0.1.0 | String to display underneath the entity name
24+
| name | [KString](#keywordstring) | | v0.1.0 | Name override
25+
| secondary_info | [KString](#keywordstring) | | v0.1.0 | String to display underneath the entity name
2626
| attributes | [Attribute](#attribute)[] | | v0.1.0 | Attributes to display
27+
| url | [KString](#keywordstring) \| Boolean | | v0.2.0 | Url to open on click/tap. (when `true` is used the target url becomes repo homepage)
28+
| attribute_urls | Boolean | | v0.2.0 | When set to `true` turns on default urls for all the displayed attributes
2729

2830
### Attribute
2931
| Name | Type | Default | Since | Description |
3032
|:-----|:-----|:-----|:-----|:-----|
3133
| name | String | **(required)** | v0.1.0 | Name of the attribute
3234
| icon | String | | v0.1.0 | Icon override (there are default icons for most of the available attributes)
35+
| 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`)
3336

3437
### KeywordString
3538

@@ -39,21 +42,27 @@ E.g. `"Card version {latest_release_tag}"` becomes `"Card version v1.5.0"`
3942

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

42-
Example configuration
45+
## Configuration example
46+
4347
```yaml
4448
type: 'custom:github-flexi-card'
4549
title: Github projects
4650
entities:
4751
- entity_id: sensor.battery_state_card
4852
secondary_info: 'Released {latest_release_tag}'
53+
url: "{latest_release_url}" # url taken from attribute
4954
attributes:
5055
- name: views
56+
url: true # default url to graphs/traffic
5157
- name: stargazers
5258
- name: open_issues
5359
- name: clones
60+
url: "https://my.custom.url/path"
5461
- name: forks
5562
- name: open_pull_requests
63+
url: "{latest_open_pull_request_url}" # url taken from attribute
5664
- entity_id: sensor.hideseek_mod
65+
url: true # default url - repo homepage
5766
attributes:
5867
- name: views
5968
- name: stargazers
@@ -65,4 +74,16 @@ entities:
6574
- name: views
6675
- name: stargazers
6776
- name: open_issues
77+
```
78+
79+
## How to install?
80+
81+
Install HACS and add this repo as custom repository (lovelace plugins). Click on "Install".
82+
83+
If you have a YAML mode remember to add resource entry for the js bundle in ui-lovelace.yaml:
84+
85+
```yaml
86+
resources:
87+
- url: /hacsfiles/github-flexi-card/github-flexi-card.js
88+
type: module
6889
```

build/prebuild.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,17 @@ const compileCss = async () => {
3737
return await writeFile(cssFile.replace(".css", ".ts"), 'import { css } from "../lit-element"; const styles = css`' + minimizeCss(cssCode) + '`; export default styles;');
3838
};
3939

40+
// Updates version printed in console window
41+
const updateVersion = async () => {
42+
const filePath = "src/utils.ts";
43+
const pkg = require("./../package.json");
44+
const utils = await readFile(filePath);
45+
const updatedUtils = utils.replace(/"%c GITHUB-FLEXI-CARD %c [0-9]+.[0-9]+.[0-9]+"/gm, `"%c GITHUB-FLEXI-CARD %c ${pkg.version}"`);
46+
if (utils !== updatedUtils) {
47+
await writeFile(filePath, updatedUtils);
48+
}
49+
}
50+
4051

41-
compileCss();
52+
compileCss();
53+
updateVersion();

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.1.0",
3+
"version": "0.2.0",
44
"description": "Home assistant github card",
55
"main": "dist/github-flexi-card.js",
66
"scripts": {

src/custom-elements/entity.ts

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { HomeAssistant } from "../ha-types";
22
import { html, LitElement } from "../lit-element";
33
import { IEntityConfig, IAttribute } from "../types";
4+
import { logError } from "../utils";
45

56
const replaceKeywordsWithData = (data: IMap<string>, text?: string) =>
67
text && text.replace(/\{([a-z0-9_]+)\}/g, (match, keyword) => data[keyword] !== undefined ? data[keyword] : match);
78

9+
/**
10+
* Attribute name to icon map
11+
*/
812
const nameToIconMap: IMap<string> = {
913
"open_issues": "mdi:alert-circle-outline",
1014
"open_pull_requests": "mdi:source-pull",
@@ -17,13 +21,69 @@ const nameToIconMap: IMap<string> = {
1721
"views_unique": "mdi:eye-check",
1822
}
1923

20-
const getStats = (attrib: IAttribute[], data: IMap<string>): IStat[] =>
21-
attrib.map(a => {
24+
/**
25+
* Attribute name to url path map
26+
*/
27+
const nameToUrlPathMap: IMap<string> = {
28+
"open_issues": "issues",
29+
"open_pull_requests": "pulls",
30+
"stargazers": "stargazers",
31+
"forks": "network/members",
32+
"latest_release_tag": "releases",
33+
"clones": "graphs/traffic",
34+
"clones_unique": "graphs/traffic",
35+
"views": "graphs/traffic",
36+
"views_unique": "graphs/traffic",
37+
"home": ""
38+
}
39+
40+
/**
41+
* Creates action for clickable elements
42+
*/
43+
const getAction = (attributeName: string, url: boolean | string | undefined, data: IMap<string>): Function | undefined => {
44+
switch (typeof url) {
45+
case "boolean":
46+
if (!url) {
47+
return undefined;
48+
}
49+
50+
if (!data["path"]) {
51+
logError(`Cannot build url - entity path attribute is missing`);
52+
return undefined;
53+
}
54+
55+
if (!nameToUrlPathMap[attributeName] === undefined) {
56+
logError(`Sorry url cannot be built for "${attributeName}"`);
57+
return undefined;
58+
}
59+
60+
return () => window.open(`https://github.com/${data["path"]}/${nameToUrlPathMap[attributeName]}`);
61+
case "string":
62+
return () => window.open(replaceKeywordsWithData(data, url));
63+
case "undefined":
64+
// we don't do anything
65+
break;
66+
default:
67+
logError("Unsupported url type: " + typeof url);
68+
}
69+
70+
return undefined;
71+
}
72+
73+
/**
74+
* Gets list of attributes data to render
75+
*/
76+
const getAttributesViewData = (config: IEntityConfig, data: IMap<string>): IAttributeViewData[] =>
77+
(config.attributes || []).map(a => {
2278
return {
2379
value: data[a.name],
2480
icon: a.icon || nameToIconMap[a.name],
2581
label: a.label && replaceKeywordsWithData(data, a.label),
26-
url: a.url && replaceKeywordsWithData(data, a.url),
82+
action: getAction(
83+
a.name,
84+
// if attrib url property is missing use the entity-level setting
85+
a.url !== undefined ? a.url : config.attribute_urls,
86+
data),
2787
}
2888
});
2989

@@ -37,14 +97,19 @@ export class GithubEntity extends LitElement {
3797

3898
private secondaryInfo: string = <any>null;
3999

40-
private stats: IStat[] = [];
100+
private attributesData: IAttributeViewData[] = [];
101+
102+
private action: Function | undefined;
103+
104+
private url: string | boolean | undefined;
41105

42106
static get properties() {
43107
return {
44108
icon: { type: String },
45109
name: { type: String },
46110
secondaryInfo: { type: String },
47111
stats: { type: Array },
112+
action: { type: Function },
48113
};
49114
}
50115

@@ -68,24 +133,33 @@ export class GithubEntity extends LitElement {
68133
this.secondaryInfo = replaceKeywordsWithData(entityData.attributes, this.config.secondary_info) as string;
69134
}
70135

71-
const newStats = getStats(this.config.attributes || [], entityData.attributes);
136+
const newStats = getAttributesViewData(this.config, entityData.attributes);
72137
// check to avoid unnecessary re-rendering
73-
if (JSON.stringify(newStats) != JSON.stringify(this.stats)) {
74-
this.stats = newStats;
138+
if (JSON.stringify(newStats) != JSON.stringify(this.attributesData)) {
139+
this.attributesData = newStats;
140+
}
141+
142+
// check whether we need to update the action
143+
if (this.url != this.config.url) {
144+
this.url = this.config.url;
145+
this.action = getAction("home", this.url, entityData.attributes);
75146
}
76147
}
77148

78149
setConfig(config: IEntityConfig) {
79150
const oldConfig = JSON.stringify(this.config);
80151
const newConfig = JSON.stringify(config);
81152

82-
if (oldConfig != newConfig) {
83-
this.config = config;
84-
85-
this.name = config.name || config.entity_id;
86-
config.icon && (this.icon = config.icon);
87-
config.secondary_info && (this.secondaryInfo = config.secondary_info);
153+
if (oldConfig == newConfig) {
154+
return;
88155
}
156+
157+
// we cannot just assign the config because it is immutable and we want to change it
158+
this.config = JSON.parse(newConfig);
159+
160+
this.name = config.name || config.entity_id;
161+
config.icon && (this.icon = config.icon);
162+
config.secondary_info && (this.secondaryInfo = config.secondary_info);
89163
}
90164

91165
createRenderRoot() {
@@ -98,11 +172,11 @@ export class GithubEntity extends LitElement {
98172
<div class="icon">
99173
<ha-icon icon="${this.icon}"></ha-icon>
100174
</div>
101-
<div class="name truncate">
175+
<div class="name truncate${this.action ? " clickable" : ""}" @click="${this.action}">
102176
${this.name}
103177
${this.secondaryInfo && html`<div class="secondary">${this.secondaryInfo}</div>`}
104178
</div>
105-
${this.stats.map(s => html`<div class="state"><ha-icon icon="${s.icon}" style="color: var(--primary-color)"></ha-icon><div>${s.value}</div></div>`)}
179+
${this.attributesData.map(s => html`<div class="state${s.action ? " clickable" : ""}" @click="${s.action}"><ha-icon icon="${s.icon}" style="color: var(--primary-color)"></ha-icon><div>${s.value}</div></div>`)}
106180
<div>
107181
`;
108182
}
@@ -112,9 +186,9 @@ interface IMap<T> {
112186
[key: string]: T
113187
}
114188

115-
interface IStat {
189+
interface IAttributeViewData {
116190
value: string,
117191
icon?: string,
118192
label?: string,
119-
url?: string,
193+
action?: Function,
120194
}

src/custom-elements/styles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,8 @@
6262
}
6363
.compact-view .state > * {
6464
margin: 0;
65+
}
66+
67+
.clickable {
68+
cursor: pointer;
6569
}

src/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ export interface ICardConfig {
55

66
export interface IEntityConfig {
77
entity_id: string,
8-
attributes: IAttribute[],
8+
attributes?: IAttribute[],
99
name?: string,
1010
secondary_info?: string,
1111
icon?: string,
12+
url?: string | boolean,
13+
attribute_urls?: boolean,
1214
}
1315

1416
export interface IAttribute {
1517
name: string,
1618
icon?: string,
1719
label?: string,
18-
url?: string,
20+
url?: string | boolean,
1921
}

src/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export const printVersion = () => console.info(
2-
"%c GITHUB-FLEXI-CARD %c 0.1.0",
2+
"%c GITHUB-FLEXI-CARD %c 0.2.0",
33
"color: white; background: #cca900; font-weight: 700;",
44
"color: #cca900; background: white; font-weight: 700;",
5-
)
5+
);
6+
7+
export const logError = (msg: string) => console.error(`[github-flexi-card] ${msg}`);

0 commit comments

Comments
 (0)