Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feat: Sensors View #64

Merged
merged 25 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2a2859c
feat: add preview activities for existing sensors
michigg Feb 11, 2024
a6ed019
chore: add network sensor
michigg Feb 11, 2024
c87bf00
chore: add device motion sensor
michigg Feb 11, 2024
018d192
chore: add battery sensor
michigg Feb 11, 2024
d8b7958
chore: start implementing ambilight sensor
michigg Feb 11, 2024
ed63839
chore: optimize deviceMotion presentation
michigg Feb 11, 2024
d000ea6
fix: ambientlight sensor vue ref handling
michigg Feb 11, 2024
1461b81
chore: add GenericSensor sensors
michigg Mar 10, 2024
6b56c65
chore: update package lock
michigg Mar 10, 2024
2b25e15
chore: move sensor list link to settings page
michigg Jun 21, 2024
2e0a8d3
chore: remove prime vue and update deps
michigg Jun 21, 2024
75ce294
chore: remove duplicate index.html
michigg Jun 21, 2024
c9a70fc
chore: add error handling to preview activities
michigg Jun 21, 2024
f766323
Merge branch 'main' into feat/sensors-view
michigg Jun 21, 2024
a9c95f5
chore: update package lock
michigg Jun 21, 2024
1f08b14
fix: various linter errors
michigg Jun 21, 2024
e305b6d
chore: try to introduce threejs animation for orientation sensor
michigg Jun 23, 2024
3052a27
chore: add audio graph to microphone test screen
michigg Jun 23, 2024
ceb1155
chore: apply the quaternion with the invert
michigg Jun 23, 2024
c0a3c6e
chore: use quarternion again; update rotation animation
michigg Jun 27, 2024
5a26cb8
fix: linter error
michigg Jun 27, 2024
74944f1
feat: implement new abstract sensor spec
michigg Jul 11, 2024
da62e4e
:arrow_up: chore: upgrade dependencies
michigg Jul 27, 2024
5358e23
chore: add tests to all sensors; update existing test; improve logging
michigg Jul 27, 2024
c9bfde7
:bug: fix: linter issues
michigg Jul 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
</head>

<body>
<div id="app"></div>
<noscript>
<strong>We're sorry but WebSense doesn't work without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>

Expand Down
6,319 changes: 3,213 additions & 3,106 deletions frontend/package-lock.json

Large diffs are not rendered by default.

73 changes: 39 additions & 34 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,54 @@
"Chrome > 70"
],
"dependencies": {
"@influxdata/influxdb-client-browser": "^1.33.2",
"@influxdata/influxdb-client-browser": "^1.34.0",
"@michigg/component-library": "^0.3.1",
"@types/motion-sensors-polyfill": "^0.3.5",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"axios": "^1.6.7",
"@vueuse/core": "^10.11.0",
"axios": "^1.7.2",
"bootstrap-icons": "^1.11.3",
"idb": "^8.0.0",
"leaflet": "^1.9.4",
"meyda": "^5.6.2",
"meyda": "^5.6.3",
"motion-sensors-polyfill": "^0.3.7",
"pinia": "^2.1.7",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
"pinia": "^2.2.0",
"three": "^0.167.0",
"vue": "^3.4.34",
"vue-router": "^4.4.0"
},
"devDependencies": {
"@pinia/testing": "^0.1.3",
"@rushstack/eslint-patch": "^1.7.2",
"@types/jsdom": "^21.1.6",
"@types/leaflet": "^1.9.8",
"@types/node": "^20.11.10",
"@vite-pwa/assets-generator": "^0.2.3",
"@vitejs/plugin-vue": "^5.0.3",
"@vitest/coverage-v8": "^1.2.2",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/test-utils": "^2.4.4",
"@pinia/testing": "^0.1.4",
"@rushstack/eslint-patch": "^1.10.3",
"@types/jsdom": "^21.1.7",
"@types/leaflet": "^1.9.12",
"@types/node": "^20.14.12",
"@types/three": "^0.167.0",
"@vite-pwa/assets-generator": "^0.2.4",
"@vitejs/plugin-vue": "^5.1.0",
"@vitest/coverage-v8": "^2.0.4",
"@vitest/ui": "^2.0.4",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.17",
"c8": "^9.1.0",
"cypress": "^13.6.3",
"eslint": "^8.56.0",
"eslint-plugin-cypress": "^2.15.1",
"eslint-plugin-vue": "^9.20.1",
"jsdom": "^24.0.0",
"autoprefixer": "^10.4.19",
"c8": "^10.1.2",
"cypress": "^13.13.1",
"eslint": "^8.57.0",
"eslint-plugin-cypress": "^3.4.0",
"eslint-plugin-vue": "^9.27.0",
"jsdom": "^24.1.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.33",
"sass": "^1.70.0",
"start-server-and-test": "^2.0.3",
"tailwindcss": "^3.4.1",
"typescript": "~5.3.3",
"vite": "^5.0.12",
"vite-plugin-pwa": "^0.17.5",
"vitest": "^1.2.2",
"vue-tsc": "^1.8.27",
"workbox-build": "^7.0.0",
"workbox-window": "^7.0.0"
"postcss": "^8.4.40",
"sass": "^1.77.8",
"start-server-and-test": "^2.0.4",
"tailwindcss": "^3.4.7",
"typescript": "~5.5.4",
"vite": "^5.3.5",
"vite-plugin-pwa": "^0.20.1",
"vitest": "^2.0.4",
"vue-tsc": "^2.0.29",
"workbox-build": "^7.1.1",
"workbox-window": "^7.1.0"
}
}
17 changes: 0 additions & 17 deletions frontend/public/index.html

This file was deleted.

3 changes: 2 additions & 1 deletion frontend/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@

:root {
/* Shadows */
--shadow-inset-md: inset 0 0.125rem .5rem hsl(var(--hue) 100% 10%)
--shadow-inset-md: inset 0 0.125rem .5rem hsl(var(--hue) 100% 10%);
--color-surface-button: var(--color-primary);
}

@font-face {
Expand Down
23 changes: 13 additions & 10 deletions frontend/src/modules/inputs/components/InputIcons.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { mount, VueWrapper } from "@vue/test-utils"
import {flushPromises, mount, VueWrapper} from "@vue/test-utils"
import InputIcons from "@/modules/inputs/components/InputIcons.vue"
import type { Sensor } from "@/modules/inputs/models/Sensor"
import { SurveySensor } from "@/modules/inputs/models/sensors/survey/Sensor"
import { MicSensor } from "@/modules/inputs/models/sensors/microphone/Sensor"
import { GeolocationSensor } from "@/modules/inputs/models/sensors/geolocation/Sensor"
Expand All @@ -9,14 +8,13 @@ import { describe, beforeEach, it, expect, vi } from "vitest"
import { createTestingPinia } from "@pinia/testing"
import { useSensorStore } from "../store"
import { DummySensor } from "@/modules/inputs/models/sensors/dummy/Sensor"
import {markRaw, type Raw} from "vue"
import type {AbstractSensorType} from "@/modules/inputs/models/sensors/abstractSensor"

describe("InputIcons", () => {
let wrapper: VueWrapper

beforeEach(() => {
const mic = new MicSensor()
mic.isAvailable = true

wrapper = mount(InputIcons, {
props: {
inputTypes: [InputType.MIC, InputType.SURVEY, InputType.DUMMY]
Expand All @@ -26,11 +24,11 @@ describe("InputIcons", () => {
}
})
const store = useSensorStore()
store.sensors = new Map<InputType, Sensor>([
[InputType.SURVEY, new SurveySensor()],
[InputType.MIC, mic],
[InputType.GEOLOCATION, new GeolocationSensor()],
[InputType.DUMMY, new DummySensor()]
store.sensors = new Map<InputType, Raw<AbstractSensorType>>([
[InputType.SURVEY, markRaw(new SurveySensor())] as [InputType, Raw<AbstractSensorType>],
[InputType.MIC, markRaw(new MicSensor())] as [InputType, Raw<AbstractSensorType>],
[InputType.GEOLOCATION, markRaw(new GeolocationSensor())] as [InputType, Raw<AbstractSensorType>],
[InputType.DUMMY, markRaw(new DummySensor())] as [InputType, Raw<AbstractSensorType>]
])
})

Expand All @@ -40,6 +38,11 @@ describe("InputIcons", () => {
})

it("renders available sensors as success", async () => {
const store = useSensorStore()
store.sensors.get(InputType.SURVEY).isAvailable.value = true
store.sensors.get(InputType.MIC).isAvailable.value = true
await flushPromises()

const icons = wrapper.findAll(".input-icon.text-success")
expect(icons.length).toBe(2)
})
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/modules/inputs/components/InputIcons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
v-for="[key, input] in inputs"
:key="key"
:class="[
input != null && input.isAvailable ? 'text-success' : 'text-danger',
input.isAvailable.value ? 'text-success' : 'text-danger',
'input-icon',
]"
>
<!-- TODO: use InputIcon component instead of custom implementation -->
<BaseIconBadge
class="input-icon-badge"
:icon-key="
input.isAvailable ? input.availableIconKey : input.unavailableIconKey
"
:icon-key="input.iconKey"
/>
</li>
</BaseInlineList>
Expand Down
34 changes: 24 additions & 10 deletions frontend/src/modules/inputs/components/InputList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,41 @@
<IconStateListElement
v-for="[key, input] in inputs"
:key="key"
:icon-key="
input.isAvailable ? input.availableIconKey : input.unavailableIconKey
"
:icon-key="input.iconKey"
:label="input.key.toUpperCase()"
:fail="!input.isAvailable"
:success="input.isAvailable"
:fail="!input.isAvailable.value"
:success="input.isAvailable.value"
data-test="sensor-list-element"
/>
>
<template
v-if="input.isAvailable.value"
#postfix
>
<RouterLink :to="{ name: 'SensorPreview', params: { inputType: input.key.toUpperCase()}}">
<BaseIcon icon-key="bi-arrow-right-circle-fill" />
</RouterLink>
</template>
</IconStateListElement>
</BaseList>
</template>

<script lang="ts" setup>
import IconStateListElement from "@/shared/components/IconStateListElement.vue"
import { computed } from "vue"
import type { InputType } from "@/modules/inputs/models/inputType"
import { useSensorStore } from "@/modules/inputs/store"
import { BaseList } from "@michigg/component-library"
import {computed} from "vue"
import type {InputType} from "@/modules/inputs/models/inputType"
import {useSensorStore} from "@/modules/inputs/store"
import {BaseIcon, BaseList} from "@michigg/component-library"

const props = defineProps<{
inputTypes: Array<InputType>
}>()
const sensorStore = useSensorStore()
const inputs = computed(() => sensorStore.getSensorsFromKeys(props.inputTypes))
</script>
<style scoped>
.bi-search {
display: block;
color: white;
width: 10px;
}
</style>
36 changes: 36 additions & 0 deletions frontend/src/modules/inputs/composables/useSensorPreview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type {ShallowRef} from "vue"
import {onMounted, type Ref, ref, shallowRef} from "vue"
import {useRoute} from "vue-router"
import type {InputType} from "@/modules/inputs/models/inputType"
import {useSensorStore} from "@/modules/inputs/store"
import type {AbstractSensor} from "@/modules/inputs/models/sensors/abstractSensor"

export function useSensorPreview(): {
sensor: Ref<AbstractSensor<unknown, unknown, unknown> | undefined>
previewActivity: ShallowRef<object | undefined>
} {
const route = useRoute()
const sensor = ref<AbstractSensor<unknown, unknown, unknown> | undefined>()
const previewActivity = shallowRef<object | undefined>(undefined)

onMounted(async () => {
console.log("Display preview")
const inputType = route.params.inputType as InputType
const sensorStore = useSensorStore()
sensor.value = sensorStore.getSensor(inputType)
console.log('Sensor', sensor.value, sensor.value?.isAvailable.value)
if (!sensor.value || !sensor.value.sensorPath) {
console.error("Preview cannot be loaded")
return // TODO: throw error?
}
const value = await import(`../models/sensors/${sensor.value.sensorPath}/PreviewActivity.vue`)

previewActivity.value = value.default
})


return {
previewActivity,
sensor
}
}
1 change: 1 addition & 0 deletions frontend/src/modules/inputs/models/Sensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface Sensor {
readonly key: InputType
readonly availableIconKey: string
readonly unavailableIconKey: string
readonly sensorPath?: string
isAvailable: boolean
isActive: boolean
isCalibrated: boolean
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/modules/inputs/models/inputType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ export enum InputType {
MIC = "MIC",
// Geolocation API https://www.webondevices.com/9-javascript-apis-accessing-device-sensors/#geolocation-api
GEOLOCATION = "GEOLOCATION",
ACCELEROMETER = "Accelerometer",
GYROSCOPE = "Gyroscope",
LINEAR_ACCELERATION = "LinearAccelerationSensor",
ABSOLUTE_ORIENTATION = "AbsoluteOrientationSensor",
RELATIVE_ORIENTATION = "RelativeOrientationSensor",
GRAVITY = "GravitySensor",
MAGNETOMETER = "Magnetometer",
ACCELEROMETER = "ACCELEROMETER",
GYROSCOPE = "GYROSCOPE",
LINEAR_ACCELERATION = "LINEAR_ACCELERATION",
ABSOLUTE_ORIENTATION = "ABSOLUTE_ORIENTATION",
RELATIVE_ORIENTATION = "RELATIVE_ORIENTATION",
GRAVITY = "GRAVITY",
MAGNETOMETER = "MAGNETOMETER",
// Device Light API https://www.webondevices.com/9-javascript-apis-accessing-device-sensors/#device-light-api
DEVICE_LIGHT = "DEVICE_LIGHT",
// Battery Manager API https://www.webondevices.com/9-javascript-apis-accessing-device-sensors/#battery-manager-api
// VueUse https://vueuse.org/core/useBattery/
BATTERY = "BATTERY",
// Proximity Events API https://www.webondevices.com/9-javascript-apis-accessing-device-sensors/#proximity-events-api
PROXIMITY = "PROXIMITY",
CAMERA = "CAMERA",
// VueUse https://vueuse.org/core/useNetwork/
NETWORK = "NETWORK",
// VueUse https://vueuse.org/core/useDeviceMotion/
DEVICE_MOTION = "DEVICE_MOTION"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<p>
Source:
<a
href="https://developer.mozilla.org/en-US/docs/Web/API/AbsoluteOrientationSensor"
rel="noopener noreferrer"
target="_blank"
>
https://developer.mozilla.org/en-US/docs/Web/API/AbsoluteOrientationSensor
</a>
</p>
<h2>Sensor Results</h2>
<ErrorCard :error="error" />
<BaseList v-if="currentSensorValue">
<KeyValueListItem
key-data="Quaternion"
:value-data="(currentSensorValue as Quaternion | undefined)"
/>
</BaseList>
<OrientationAnimation
v-if="currentSensorValue"
:quaternion="(currentSensorValue as Quaternion)"
/>
</template>

<script lang="ts" setup>
import {BaseList, ErrorCard} from "@michigg/component-library"
import KeyValueListItem from "@/modules/log/components/KeyValueListItem.vue"
import type {
Quaternion,
WebSenseAbsoluteOrientationSensor
} from "@/modules/inputs/models/sensors/absoluteOrientationSensor/Sensor"
import OrientationAnimation from "@/shared/components/OrientationAnimation.vue"
import type {AbstractSensorType} from "@/modules/inputs/models/sensors/abstractSensor"
import {onUnmounted} from "vue"

// Access sensor
const props = defineProps<{
sensor: AbstractSensorType
}>()

const absoluteOrientationSensor = props.sensor as WebSenseAbsoluteOrientationSensor
absoluteOrientationSensor.start({frequency: 60, referenceFrame: 'device'})
const { currentSensorValue, error } = absoluteOrientationSensor

onUnmounted(() => {
absoluteOrientationSensor.stop()
})
</script>

<style scoped></style>
Loading