Add timer card and badge (#810)

* Add timer card and badge

* Disable interval on disconnect

* Tests!

* One more test case

* Remove padStart

* Remove state from timer state card
This commit is contained in:
Paulus Schoutsen 2018-01-19 09:26:06 -08:00 committed by GitHub
parent 85d58ba134
commit 783f356679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 318 additions and 26 deletions

View File

@ -1,5 +1,6 @@
{ {
"rules": { "rules": {
"import/no-extraneous-dependencies": 0,
"no-restricted-syntax": 0, "no-restricted-syntax": 0,
"no-console": 0 "no-console": 0
} }

View File

@ -0,0 +1,4 @@
export default function durationToSeconds(duration) {
const parts = duration.split(':').map(Number);
return (parts[0] * 3600) + (parts[1] * 60) + parts[2];
}

View File

@ -0,0 +1,16 @@
const leftPad = number => (number < 10 ? `0${number}` : number);
export default function secondsToDuration(d) {
const h = Math.floor(d / 3600);
const m = Math.floor((d % 3600) / 60);
const s = Math.floor(d % 3600 % 60);
if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
} else if (m > 0) {
return `${m}:${leftPad(s)}`;
} else if (s > 0) {
return '' + s;
}
return null;
}

View File

@ -11,6 +11,7 @@ const DOMAINS_WITH_CARD = [
'media_player', 'media_player',
'scene', 'scene',
'script', 'script',
'timer',
'weblink', 'weblink',
]; ];

View File

@ -0,0 +1,13 @@
import durationToSeconds from './duration_to_seconds.js';
export default function timerTimeRemaining(stateObj) {
let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
if (stateObj.state === 'active') {
const now = new Date();
const madeActive = new Date(stateObj.last_changed);
timeRemaining = Math.max(timeRemaining - ((now - madeActive) / 1000), 0);
}
return timeRemaining;
}

View File

@ -9,14 +9,17 @@
import attributeClassNames from './common/util/attribute_class_names.js'; import attributeClassNames from './common/util/attribute_class_names.js';
import canToggleDomain from './common/util/can_toggle_domain.js'; import canToggleDomain from './common/util/can_toggle_domain.js';
import canToggleState from './common/util/can_toggle_state.js'; import canToggleState from './common/util/can_toggle_state.js';
import computeStateDomain from './common/util/compute_state_domain.js';
import computeStateDisplay from './common/util/compute_state_display.js'; import computeStateDisplay from './common/util/compute_state_display.js';
import computeDomain from './common/util/compute_state_domain.js';
import durationToSeconds from './common/util/duration_to_seconds.js';
import featureClassNames from './common/util/feature_class_names.js'; import featureClassNames from './common/util/feature_class_names.js';
import formatDate from './common/util/format_date.js'; import formatDate from './common/util/format_date.js';
import formatDateTime from './common/util/format_date_time.js'; import formatDateTime from './common/util/format_date_time.js';
import formatTime from './common/util/format_time.js'; import formatTime from './common/util/format_time.js';
import secondsToDuration from './common/util/seconds_to_duration.js';
import stateCardType from './common/util/state_card_type.js'; import stateCardType from './common/util/state_card_type.js';
import stateMoreInfoType from './common/util/state_more_info_type.js'; import stateMoreInfoType from './common/util/state_more_info_type.js';
import timerTimeRemaining from './common/util/timer_time_remaining.js';
window.hassUtil = window.hassUtil || {}; window.hassUtil = window.hassUtil || {};
@ -25,14 +28,19 @@ const language = navigator.languages ?
window.fecha.masks.haDateTime = window.fecha.masks.shortTime + ' ' + window.fecha.masks.mediumDate; window.fecha.masks.haDateTime = window.fecha.masks.shortTime + ' ' + window.fecha.masks.mediumDate;
window.hassUtil.attributeClassNames = attributeClassNames; Object.assign(window.hassUtil, {
window.hassUtil.canToggleDomain = canToggleDomain; attributeClassNames,
window.hassUtil.canToggleState = canToggleState; canToggleDomain,
window.hassUtil.computeDomain = computeStateDomain; canToggleState,
window.hassUtil.computeStateDisplay = computeStateDisplay; computeDomain,
window.hassUtil.featureClassNames = featureClassNames; computeStateDisplay,
window.hassUtil.formatDate = dateObj => formatDate(dateObj, language); durationToSeconds,
window.hassUtil.formatDateTime = dateObj => formatDateTime(dateObj, language); featureClassNames,
window.hassUtil.formatTime = dateObj => formatTime(dateObj, language); secondsToDuration,
window.hassUtil.stateCardType = stateCardType; stateCardType,
window.hassUtil.stateMoreInfoType = stateMoreInfoType; stateMoreInfoType,
timerTimeRemaining,
formatDate: dateObj => formatDate(dateObj, language),
formatDateTime: dateObj => formatDateTime(dateObj, language),
formatTime: dateObj => formatTime(dateObj, language),
});

View File

@ -23,6 +23,13 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"es6-object-assign": "^1.1.0",
"home-assistant-js-websocket": "^1.1.2",
"mdn-polyfills": "^5.5.0",
"preact": "^8.2.6",
"unfetch": "^3.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-plugin-external-helpers": "^6.22.0", "babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0",
@ -32,7 +39,6 @@
"chai": "^4.1.2", "chai": "^4.1.2",
"css-slam": "^2.0.2", "css-slam": "^2.0.2",
"del": "^3.0.0", "del": "^3.0.0",
"es6-object-assign": "^1.1.0",
"eslint": "^4.11.0", "eslint": "^4.11.0",
"eslint-config-airbnb-base": "^12.1.0", "eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-html": "^3.2.2", "eslint-plugin-html": "^3.2.2",
@ -58,9 +64,7 @@
"gulp-util": "^3.0.8", "gulp-util": "^3.0.8",
"gulp-vinyl-zip": "^2.1.0", "gulp-vinyl-zip": "^2.1.0",
"gulp-zopfli": "^1.0.0", "gulp-zopfli": "^1.0.0",
"home-assistant-js-websocket": "^1.1.2",
"html-minifier": "^3.5.6", "html-minifier": "^3.5.6",
"mdn-polyfills": "^5.5.0",
"merge-stream": "^1.0.1", "merge-stream": "^1.0.1",
"mocha": "^4.0.1", "mocha": "^4.0.1",
"parse5": "^3.0.3", "parse5": "^3.0.3",
@ -68,7 +72,6 @@
"polymer-build": "^2.1.0", "polymer-build": "^2.1.0",
"polymer-bundler": "^3.1.0", "polymer-bundler": "^3.1.0",
"polymer-cli": "^1.5.6", "polymer-cli": "^1.5.6",
"preact": "^8.2.6",
"pump": "^1.0.2", "pump": "^1.0.2",
"reify": "^0.12.3", "reify": "^0.12.3",
"require-dir": "^0.3.2", "require-dir": "^0.3.2",
@ -79,12 +82,10 @@
"rollup-plugin-replace": "^2.0.0", "rollup-plugin-replace": "^2.0.0",
"rollup-watch": "^4.3.1", "rollup-watch": "^4.3.1",
"run-sequence": "^2.2.0", "run-sequence": "^2.2.0",
"sinon": "^4.1.6",
"sw-precache": "^5.2.0", "sw-precache": "^5.2.0",
"uglify-es": "^3.1.9", "uglify-es": "^3.1.9",
"uglify-js": "^3.1.9", "uglify-js": "^3.1.9",
"unfetch": "^3.0.0"
},
"devDependencies": {
"web-component-tester": "^6.4.0" "web-component-tester": "^6.4.0"
} }
} }

View File

@ -40,7 +40,7 @@
value='[[computeValue(state)]]' value='[[computeValue(state)]]'
icon='[[computeIcon(state)]]' icon='[[computeIcon(state)]]'
image='[[computeImage(state)]]' image='[[computeImage(state)]]'
label='[[computeLabel(localize, state)]]' label='[[computeLabel(localize, state, timerTimeRemaining)]]'
description='[[computeDescription(state)]]' description='[[computeDescription(state)]]'
></ha-label-badge> ></ha-label-badge>
</template> </template>
@ -62,9 +62,23 @@ class HaStateLabelBadge extends
type: Object, type: Object,
observer: 'stateChanged', observer: 'stateChanged',
}, },
timerTimeRemaining: {
type: Number,
value: 0,
}
}; };
} }
connectedCallback() {
super.connectedCallback();
this.startInterval(this.state);
}
disconnectedCallback() {
super.disconnectedCallback();
this.clearInterval();
}
ready() { ready() {
super.ready(); super.ready();
this.addEventListener('tap', ev => this.badgeTap(ev)); this.addEventListener('tap', ev => this.badgeTap(ev));
@ -92,6 +106,7 @@ class HaStateLabelBadge extends
case 'updater': case 'updater':
case 'sun': case 'sun':
case 'alarm_control_panel': case 'alarm_control_panel':
case 'timer':
return null; return null;
case 'sensor': case 'sensor':
default: default:
@ -128,6 +143,8 @@ class HaStateLabelBadge extends
case 'sun': case 'sun':
return state.state === 'above_horizon' ? return state.state === 'above_horizon' ?
window.hassUtil.domainIcon(domain) : 'mdi:brightness-3'; window.hassUtil.domainIcon(domain) : 'mdi:brightness-3';
case 'timer':
return state.state === 'active' ? 'mdi:timer' : 'mdi:timer-off';
default: default:
return null; return null;
} }
@ -137,7 +154,7 @@ class HaStateLabelBadge extends
return state.attributes.entity_picture || null; return state.attributes.entity_picture || null;
} }
computeLabel(localize, state) { computeLabel(localize, state, timerTimeRemaining) {
const domain = window.hassUtil.computeDomain(state); const domain = window.hassUtil.computeDomain(state);
if (state.state === 'unavailable' || if (state.state === 'unavailable' ||
['device_tracker', 'alarm_control_panel'].includes(domain)) { ['device_tracker', 'alarm_control_panel'].includes(domain)) {
@ -146,6 +163,9 @@ class HaStateLabelBadge extends
// are only added for device_tracker and alarm_control_panel. // are only added for device_tracker and alarm_control_panel.
return localize(`state_badge.${domain}.${state.state}`) || localize(`state_badge.default.${state.state}`) || state.state; return localize(`state_badge.${domain}.${state.state}`) || localize(`state_badge.default.${state.state}`) || state.state;
} }
if (domain === 'timer') {
return window.hassUtil.secondsToDuration(timerTimeRemaining);
}
return state.attributes.unit_of_measurement || null; return state.attributes.unit_of_measurement || null;
} }
@ -153,8 +173,31 @@ class HaStateLabelBadge extends
return window.hassUtil.computeStateName(state); return window.hassUtil.computeStateName(state);
} }
stateChanged() { stateChanged(stateObj) {
this.updateStyles(); this.updateStyles();
this.startInterval(stateObj);
}
clearInterval() {
if (this._updateRemaining) {
clearInterval(this._updateRemaining);
this._updateRemaining = null;
}
}
startInterval(stateObj) {
this.clearInterval();
if (window.hassUtil.computeDomain(stateObj) === 'timer') {
this.calculateTimerRemaining(stateObj);
if (stateObj.state === 'active') {
this._updateRemaining = setInterval(() => this.calculateTimerRemaining(this.state), 1000);
}
}
}
calculateTimerRemaining(stateObj) {
this.timerTimeRemaining = window.hassUtil.timerTimeRemaining(stateObj);
} }
} }

View File

@ -106,6 +106,7 @@
sun: 1, sun: 1,
device_tracker: 2, device_tracker: 2,
alarm_control_panel: 3, alarm_control_panel: 3,
timer: 4,
sensor: 5, sensor: 5,
binary_sensor: 6, binary_sensor: 6,
mailbox: 7, mailbox: 7,

View File

@ -10,6 +10,7 @@
<link rel="import" href="state-card-media_player.html"> <link rel="import" href="state-card-media_player.html">
<link rel="import" href="state-card-scene.html"> <link rel="import" href="state-card-scene.html">
<link rel="import" href="state-card-script.html"> <link rel="import" href="state-card-script.html">
<link rel="import" href="state-card-timer.html">
<link rel="import" href="state-card-toggle.html"> <link rel="import" href="state-card-toggle.html">
<link rel="import" href="state-card-weblink.html"> <link rel="import" href="state-card-weblink.html">

View File

@ -0,0 +1,88 @@
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../components/entity/state-info.html">
<link rel="import" href="../util/hass-mixins.html">
<link rel="import" href="../util/hass-util.html">
<dom-module id="state-card-timer">
<template>
<style is="custom-style" include="iron-flex iron-flex-alignment"></style>
<style>
.state {
@apply(--paper-font-body1);
color: var(--primary-text-color);
margin-left: 16px;
text-align: right;
line-height: 40px;
}
</style>
<div class='horizontal justified layout'>
<state-info state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-info>
<div class='state'>[[secondsToDuration(timeRemaining)]]</div>
</div>
</template>
</dom-module>
<script>
class StateCardTimer extends window.hassMixins.LocalizeMixin(Polymer.Element) {
static get is() { return 'state-card-timer'; }
static get properties() {
return {
hass: Object,
stateObj: {
type: Object,
observer: 'stateObjChanged',
},
timeRemaining: Number,
inDialog: {
type: Boolean,
value: false,
},
};
}
connectedCallback() {
super.connectedCallback();
this.startInterval(this.stateObj);
}
disconnectedCallback() {
super.disconnectedCallback();
this.clearInterval();
}
stateObjChanged(stateObj) {
this.startInterval(stateObj);
}
clearInterval() {
if (this._updateRemaining) {
clearInterval(this._updateRemaining);
this._updateRemaining = null;
}
}
startInterval(stateObj) {
this.clearInterval();
this.calculateRemaining(stateObj);
if (stateObj.state === 'active') {
this._updateRemaining = setInterval(() => this.calculateRemaining(this.stateObj), 1000);
}
}
calculateRemaining(stateObj) {
this.timeRemaining = window.hassUtil.timerTimeRemaining(stateObj);
}
secondsToDuration(time) {
return window.hassUtil.secondsToDuration(time);
}
}
customElements.define(StateCardTimer.is, StateCardTimer);
</script>

View File

@ -187,6 +187,9 @@ window.hassUtil.domainIcon = function (domain, state) {
case 'switch': case 'switch':
return 'mdi:flash'; return 'mdi:flash';
case 'timer':
return 'mdi:timer';
case 'updater': case 'updater':
return 'mdi:cloud-upload'; return 'mdi:cloud-upload';

View File

@ -1,5 +1,8 @@
{ {
"env": { "env": {
"mocha": true "mocha": true
},
"rules": {
"import/no-extraneous-dependencies": 0
} }
} }

View File

@ -0,0 +1,10 @@
import { assert } from 'chai';
import durationToSeconds from '../../../js/common/util/duration_to_seconds.js';
describe('durationToSeconds', () => {
it('works', () => {
assert.strictEqual(durationToSeconds('0:01:05'), 65);
assert.strictEqual(durationToSeconds('11:01:05'), 39665);
});
});

View File

@ -0,0 +1,12 @@
import { assert } from 'chai';
import secondsToDuration from '../../../js/common/util/seconds_to_duration.js';
describe('secondsToDuration', () => {
it('works', () => {
assert.strictEqual(secondsToDuration(0), null);
assert.strictEqual(secondsToDuration(65), '1:05');
assert.strictEqual(secondsToDuration(3665), '1:01:05');
assert.strictEqual(secondsToDuration(39665), '11:01:05');
});
});

View File

@ -0,0 +1,43 @@
import { assert } from 'chai';
import sinon from 'sinon';
import timerTimeRemaining from '../../../js/common/util/timer_time_remaining.js';
describe('timerTimeRemaining', () => {
it('works with idle timers', () => {
assert.strictEqual(timerTimeRemaining({
state: 'idle',
attributes: {
remaining: '0:01:05'
}
}), 65);
});
it('works with paused timers', () => {
assert.strictEqual(timerTimeRemaining({
state: 'paused',
attributes: {
remaining: '0:01:05'
}
}), 65);
});
describe('active timers', () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers(new Date('2018-01-17T16:15:30Z'));
});
afterEach(() => {
clock.restore();
});
it('works', () => {
assert.strictEqual(timerTimeRemaining({
state: 'active',
attributes: {
remaining: '0:01:05'
},
last_changed: '2018-01-17T16:15:12Z',
}), 47);
});
});
});

View File

@ -3275,7 +3275,7 @@ formatio@1.1.1:
dependencies: dependencies:
samsam "~1.1" samsam "~1.1"
formatio@1.2.0: formatio@1.2.0, formatio@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb"
dependencies: dependencies:
@ -4679,6 +4679,10 @@ jsx-ast-utils@^2.0.0:
dependencies: dependencies:
array-includes "^3.0.3" array-includes "^3.0.3"
just-extend@^1.1.26:
version "1.1.27"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
kind-of@^3.0.2: kind-of@^3.0.2:
version "3.2.2" version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@ -4968,6 +4972,10 @@ lodash.escape@~2.4.1:
lodash._reunescapedhtml "~2.4.1" lodash._reunescapedhtml "~2.4.1"
lodash.keys "~2.4.1" lodash.keys "~2.4.1"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
lodash.identity@~2.4.1: lodash.identity@~2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/lodash.identity/-/lodash.identity-2.4.1.tgz#6694cffa65fef931f7c31ce86c74597cf560f4f1" resolved "https://registry.yarnpkg.com/lodash.identity/-/lodash.identity-2.4.1.tgz#6694cffa65fef931f7c31ce86c74597cf560f4f1"
@ -5137,6 +5145,10 @@ lolex@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6"
lolex@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.1.tgz#3d2319894471ea0950ef64692ead2a5318cff362"
longest@^1.0.1: longest@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@ -5544,6 +5556,16 @@ netrc@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/netrc/-/netrc-0.1.4.tgz#6be94fcaca8d77ade0a9670dc460914c94472444" resolved "https://registry.yarnpkg.com/netrc/-/netrc-0.1.4.tgz#6be94fcaca8d77ade0a9670dc460914c94472444"
nise@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53"
dependencies:
formatio "^1.2.0"
just-extend "^1.1.26"
lolex "^1.6.0"
path-to-regexp "^1.7.0"
text-encoding "^0.6.4"
no-case@^2.2.0: no-case@^2.2.0:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081"
@ -7150,6 +7172,18 @@ sinon@^2.3.5:
text-encoding "0.6.4" text-encoding "0.6.4"
type-detect "^4.0.0" type-detect "^4.0.0"
sinon@^4.1.6:
version "4.1.6"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.1.6.tgz#9cb346bddb180d68a804429ffe14978d7fafd629"
dependencies:
diff "^3.1.0"
formatio "1.2.0"
lodash.get "^4.4.2"
lolex "^2.2.0"
nise "^1.2.0"
supports-color "^5.1.0"
type-detect "^4.0.5"
slash@^1.0.0: slash@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@ -7556,6 +7590,12 @@ supports-color@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
supports-color@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5"
dependencies:
has-flag "^2.0.0"
sw-precache@^5.1.1, sw-precache@^5.2.0: sw-precache@^5.1.1, sw-precache@^5.2.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.0.tgz#eb6225ce580ceaae148194578a0ad01ab7ea199c" resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.0.tgz#eb6225ce580ceaae148194578a0ad01ab7ea199c"
@ -7684,7 +7724,7 @@ test-value@^2.1.0:
array-back "^1.0.3" array-back "^1.0.3"
typical "^2.6.0" typical "^2.6.0"
text-encoding@0.6.4: text-encoding@0.6.4, text-encoding@^0.6.4:
version "0.6.4" version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
@ -7854,6 +7894,10 @@ type-detect@^4.0.0:
version "4.0.5" version "4.0.5"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2"
type-detect@^4.0.5:
version "4.0.6"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.6.tgz#88cbce3d13bc675a63f840b3225c180f870786d7"
type-is@^1.6.4, type-is@~1.6.15: type-is@^1.6.4, type-is@~1.6.15:
version "1.6.15" version "1.6.15"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"