From 97ec0a9a7c15da715833aa38fea012175b97d8d6 Mon Sep 17 00:00:00 2001 From: Jooger Date: Tue, 29 Oct 2019 13:22:17 +0800 Subject: [PATCH] feat: add sensor/useBattery hook --- src/hooks/sensor/useBattery/demo.story.tsx | 56 +++++++++++++ src/hooks/sensor/useBattery/doc.md | 90 ++++++++++++++++++++ src/hooks/sensor/useBattery/index.ts | 96 ++++++++++++++++++++++ src/index.ts | 1 + src/utils/index.ts | 5 +- 5 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/hooks/sensor/useBattery/demo.story.tsx create mode 100644 src/hooks/sensor/useBattery/doc.md create mode 100644 src/hooks/sensor/useBattery/index.ts diff --git a/src/hooks/sensor/useBattery/demo.story.tsx b/src/hooks/sensor/useBattery/demo.story.tsx new file mode 100644 index 0000000..075f5b9 --- /dev/null +++ b/src/hooks/sensor/useBattery/demo.story.tsx @@ -0,0 +1,56 @@ +import { storiesOf } from '@storybook/vue' +import { createComponent } from '@vue/composition-api' +import Doc from '../../../__stories__/components/Doc' +import { useBattery } from '../../..' + +const Demo = createComponent({ + setup() { + const battery = useBattery() + return { battery } + }, + render() { + const { battery } = this + + if (!battery.isSupported) { + return ( +
+ Battery sensor: not supported +
+ ) + } + + if (!battery.fetched) { + return ( +
+ Battery sensor: supported
+ Battery state: fetching +
+ ) + } + + return ( +
+ Battery sensor:   supported{' '} +
+ Battery state: fetched
+ Charge level:  {' '} + {(battery.level * 100).toFixed(0)}%
+ Charging:  {' '} + {battery.charging ? 'yes' : 'no'}
+ Charging time:   + + {battery.chargingTime ? battery.chargingTime : 'finished'} + {' '} +
+ Discharging time:  {' '} + {battery.dischargingTime} +
+ ) + } +}) + +const Docs = () => + +storiesOf('Sensor|useBattery', module) + .add('Docs', () => Docs as any) + .add('Demo', () => Demo) diff --git a/src/hooks/sensor/useBattery/doc.md b/src/hooks/sensor/useBattery/doc.md new file mode 100644 index 0000000..601cb87 --- /dev/null +++ b/src/hooks/sensor/useBattery/doc.md @@ -0,0 +1,90 @@ +# useBattery + +Vue hook that tracks device battery state. + +> **Note:** current `BatteryManager` API state is obsolete. +> Although it may still work in some browsers, its use is discouraged since it could be removed at any time. + +## Usage + +```jsx +import { createComponent } from '@vue/composition-api' +import { useBattery } from 'vuses' + +const Demo = createComponent({ + setup() { + const battery = useBattery() + return { battery } + }, + render() { + const { battery } = this + + if (!battery.isSupported) { + return ( +
+ Battery sensor: not supported +
+ ) + } + + if (!battery.fetched) { + return ( +
+ Battery sensor: supported
+ Battery state: fetching +
+ ) + } + + return ( +
+ Battery sensor:   supported{' '} +
+ Battery state: fetched
+ Charge level:  {' '} + {(battery.level * 100).toFixed(0)}%
+ Charging:   + {battery.charging ? 'yes' : 'no'} +
+ Charging time:   + + {battery.chargingTime ? battery.chargingTime : 'finished'} +
+ Discharging time:  {' '} + {battery.dischargingTime} +
+ ) + } +}) +``` + +## Reference + +```typescript +interface BatteryState { + charging: boolean + chargingTime: number + dischargingTime: number + level: number +} + +type FetchedBatteryState = BatteryState & { + isSupported: boolean + fetched: boolean +} + +type UseBatteryState = + | { isSupported: false } // Battery API is not supported + | FetchedBatteryState // battery API supported + +function useBattery(): UseBatteryState +``` + +## ReturnValue + +- **`isSupported`**_`: boolean`_ - whether browser/devise supports BatteryManager; +- **`fetched`**_`: boolean`_ - whether battery state is fetched; +- **`level`**_`: number`_ - representing the system's battery charge level scaled to a value between 0.0 and 1.0. +- **`charging`**_`: boolean`_ - indicating whether or not the battery is currently being charged. +- **`dischargingTime`**_`: number`_ - remaining time in seconds until the battery is completely discharged and the system will suspend. +- **`chargingTime`**_`: number`_ - remaining time in seconds until the battery is fully charged, or 0 if the battery is already fully charged. diff --git a/src/hooks/sensor/useBattery/index.ts b/src/hooks/sensor/useBattery/index.ts new file mode 100644 index 0000000..4253113 --- /dev/null +++ b/src/hooks/sensor/useBattery/index.ts @@ -0,0 +1,96 @@ +import { reactive, onMounted, onUnmounted } from '@vue/composition-api' +import { + checkBrowser, + addEventListener, + removeEventListener, + warn +} from '../../../utils' + +export interface BatteryState { + charging: boolean + chargingTime: number + dischargingTime: number + level: number +} + +type FetchedBatteryState = BatteryState & { + isSupported: boolean + fetched: boolean +} + +type UseBatteryState = + | { isSupported: false } // Battery API is not supported + | FetchedBatteryState // battery API supported + +export interface BatteryManager extends Readonly, EventTarget { + onchargingchange: VoidFunction + onchargingtimechange: VoidFunction + ondischargingtimechange: VoidFunction + onlevelchange: VoidFunction +} + +interface NavigatorWithPossibleBattery extends Navigator { + getBattery?: () => Promise +} + +const nav: NavigatorWithPossibleBattery | undefined = + typeof navigator === 'object' ? navigator : undefined +const isBatteryApiSupported = nav && typeof nav.getBattery === 'function' + +export default function useBattery(): UseBatteryState { + checkBrowser(useBattery.name) + + if (!isBatteryApiSupported) { + return { isSupported: false } + } + + const state = reactive({ + isSupported: true, + fetched: false, + charging: false, + chargingTime: 0, + dischargingTime: 0, + level: 0 + }) + + let mounted = true + let battery: BatteryManager | null = null + + const update = () => { + if (!mounted || !battery) { + return + } + state.isSupported = true + state.fetched = true + state.level = battery.level + state.charging = battery.charging + state.dischargingTime = battery.dischargingTime + state.chargingTime = battery.chargingTime + } + + onMounted(() => { + nav!.getBattery!() + .then((bm: BatteryManager) => { + if (!mounted) return + battery = bm + addEventListener(battery, 'chargingchange', update) + addEventListener(battery, 'chargingtimechange', update) + addEventListener(battery, 'dischargingtimechange', update) + addEventListener(battery, 'levelchange', update) + update() + }) + .catch(warn) + }) + + onUnmounted(() => { + mounted = false + if (battery) { + removeEventListener(battery, 'chargingchange', update) + removeEventListener(battery, 'chargingtimechange', update) + removeEventListener(battery, 'dischargingtimechange', update) + removeEventListener(battery, 'levelchange', update) + } + }) + + return state +} diff --git a/src/index.ts b/src/index.ts index 72da3fa..6619803 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export { default as useWindowSize } from './hooks/sensor/useWindowSize' export { default as useWindowScroll } from './hooks/sensor/useWindowScroll' export { default as useGeolocation } from './hooks/sensor/useGeolocation' export { default as useMouse } from './hooks/sensor/useMouse' +export { default as useBattery } from './hooks/sensor/useBattery' // Side Effect Hooks export { default as useTitle } from './hooks/sideEffect/useTitle' diff --git a/src/utils/index.ts b/src/utils/index.ts index 2916a31..2cd7df4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,5 @@ import { isRef, Ref } from '@vue/composition-api' +import { BatteryManager } from '../hooks/sensor/useBattery' export const isBrowser = typeof window === 'object' @@ -34,7 +35,7 @@ export const checkBrowser = (ctx = '') => { } export const addEventListener = ( - el: Element | Window | PermissionStatus | Document, + el: Element | Window | PermissionStatus | Document | BatteryManager, event: string, handler: EventListener, options?: AddEventListenerOptions @@ -43,7 +44,7 @@ export const addEventListener = ( } export const removeEventListener = ( - el: Element | Window | PermissionStatus | Document, + el: Element | Window | PermissionStatus | Document | BatteryManager, event: string, handler: EventListener, options?: EventListenerOptions